2021电设F题

  • 回顾
  • 思路及代码
  • 可优化的点
  • 尾声
  • 规则

回顾

本在大二下的电设延延期了,有幸与车队队友半途加入,笔者之前没做过摄像头与视觉的代码(之前做的AI电磁),虽然最后有各种各样遗憾,但也临时猛学收获许多新知识。测评前顺利做完了基础和发挥(1),可惜封箱后换了场地识别出了问题。
做的途中出现过以下问题:

  • 数字移动过程中识别帧率过低
  • 识别过于依赖光线亮暗
  • 琐碎的失误:赛前未全面准备(摄像头AI、软件队友应合作提前搭好代码框架)、组队分工不当(视觉与控制没分工写、两车没一起调[框架没提前搭])、测评时拆箱后未检查装置(封箱后可能压到摄像头改变角度)

思路及代码

主控英飞凌TC377,摄像头Openmv循迹,Openart识别

  • 循迹代码
    基本思路:调红色阈值,用直线回归拟合二值化后的赛道中线line,其偏移距离rho与偏移角度theta作为err;用二值化后图像中的亮度的平均值light来判别是否为分岔元素。(之前没写过openmv,判别分岔的方案不是很好,需要摄像头角度固定且阈值在各光线下较好,比完赛后想到新的思路是设置感兴趣区在左右两侧,当居中两侧有较多亮点时可判别为分岔元素
 import sensor, image, time, mathfrom image import SEARCH_EX, SEARCH_DSfrom pyb import UARTuart = UART(3, 115200)'变量预定义'global cnt_lossglobal trackcnt_loss=0track=0THRESHOLD = (14, 65, -128, 127, 22, 127)def err_revise():rho_err = int(abs(line.rho())-img.width()/2)if line.theta()>90:theta_err = line.theta()-180else:theta_err = line.theta()img.draw_line(line.line(), color = (0,0,255))theta_err=-theta_errerr = theta_err + rho_errreturn errdef track_judge():global trackglobal cnt_losslight = img.statistics().l_mean()print(light)'track:2.岔路口,1.普通红线,0.红线丢失'if (light>=23):track=2elif light<23 and light>3:track=1else:track=0'main部分'sensor.reset()sensor.set_vflip(True)sensor.set_hmirror(True)sensor.set_pixformat(sensor.RGB565)sensor.set_framesize(sensor.QQQVGA)sensor.skip_frames(time = 2000)clock = time.clock()while(True):clock.tick()img = sensor.snapshot().binary([THRESHOLD])line = img.get_regression([(100,100)], robust = True)track_judge()if (line):err=err_revise()if  track==2:uart.write( str(err)+'T' )print('T cro')elif track==1:uart.write( str(err)+'F' )print('F cro')else:uart.write( str(err)+'L' )print('loss')print(str(err)+'\n')time.sleep_ms(30)
  • 数字识别
    基本思路:参考的是逐飞官方的开源例程,在图像中找黑色边框,将框中图像输入到模型识别。第一次识别的数字通过串口发送给MCU,之后再识别到数字则根据黑框中心点位置位于左右侧来发送左右转信息给MCU。
    模型训练:基本按着逐飞官方的教程来,当时Openart到已经是第二天下午,时间很紧,好在之前做过电磁的AI,官方训练的环境用anconda配置过,流程也走过。但一下午结果训练出来效果识别准确率很差,后来用大佬的训练集自己再图像增强(就增加一些其他亮度的图片)后,准确率提升了不少,但当时感觉还是特别受环境亮度影响(后来仔细想其实主要是帧率太低,应该让车在路口停下来识别,静态识别的准确率最后测评时其实有90%+)。时间来到第二天晚,识别很不稳定,而且还没和控制结合,赶工搭了控制框架能实现1和2的入病房后,2点多队友建议回去睡觉,现在想想不知是对是错了。(车赛时的模型是自己tensorflow训练后移植,说实话电赛当时也想过自己训练个部署,但没做过openart上移植模型,自己移植模型怕时间太赶,就照着官方做,其实用openmv的tf库即可,tf.load可以加载tensorflowlite模型,只需训练模型后保存为tflite模型即可
'''数字识别Openart代码'''import pybimport sensor, image, time, mathimport os, nncufrom machine import UARTuart = UART(1, baudrate=115200)sensor.reset()sensor.set_pixformat(sensor.RGB565)sensor.set_framesize(sensor.QVGA) # we run out of memory if the resolution is much bigger...sensor.set_brightness(300) # 设置图像亮度 越大越亮sensor.skip_frames(time = 2000)sensor.set_auto_gain(False)  # must turn this off to prevent image washout...sensor.set_auto_whitebal(False,(0,0x80,0))  # must turn this off to prevent image washout...clock = time.clock()net_path = "_1s_model_17_1.0000_xxxx.nncu"                          # 定义模型的路径labels = [line.rstrip() for line in open("/sd/labels_number.txt")]  # 加载标签net = nncu.load(net_path, load_to_fb=True)                          # 加载模型first_num=0is_send_num=0while(True):img = sensor.snapshot()print("********** Start Detections **********")for r in img.find_rects(threshold = 30000):             # 在图像中搜索矩形img.draw_rectangle(r.rect(), color = (255, 0, 0))   # 绘制矩形外框,便于在IDE上查看识别到的矩形位置img1 = img.copy(r.rect())                           # 拷贝矩形框内的图像print(r.rect()[3])if r.rect()[1]>10 and r.rect()[1]<140 and r.rect()[3]>65 and r.rect()[3]<90:for obj in nncu.classify(net , img1, min_scale=1.0, scale_mul=0.5, x_overlap=0.0, y_overlap=0.0):sorted_list = sorted(zip(labels, obj.output()), key = lambda x: x[1], reverse = True)# 打印准确率最高的结果for i in range(1):if first_num==0:first_num=sorted_list[i][0]###发送数字,为避免与Openmv发送err混淆,Openart发送数字对应的字母#######if str(first_num)=='1' and is_send_num==0:uart.write('a')print('1')is_send_num=1elif str(first_num)=='2' and is_send_num==0:uart.write('b')print('2')is_send_num=1elif str(first_num)=='3' and is_send_num==0:uart.write('c')print('3')is_send_num=1elif str(first_num)=='4' and is_send_num==0:uart.write('d')print('4')is_send_num=1elif str(first_num)=='5' and is_send_num==0:uart.write('e')print('5')is_send_num=1elif str(first_num)=='6' and is_send_num==0:uart.write('f')print('6')is_send_num=1elif str(first_num)=='7' and is_send_num==0:uart.write('g')print('7')is_send_num=1elif str(first_num)=='8' and is_send_num==0:uart.write('h')print('8')sensor.set_brightness(400)is_send_num=1##################print('sco'+str(first_num))if sorted_list[i][0]==first_num:'识别到需求数字,判断位于左/右侧'if (r.rect()[0]<120):uart.write('l')print(str(first_num)+' on l')else:uart.write('r')print(str(first_num)+' on r')img.draw_string(r.rect()[0] + 20, r.rect()[1]-20, sorted_list[0][0],color = (0,0,255), scale = 2,mono_space=False)
  • 控制代码
    基本思路
    1.药品状态标志(变量名flag.drug)
标志 状态
0 初始未放药
1 放上药品后
2 病房处停下(丢红线)等待取药
3 取下药品掉头的过程
4 掉头后循线到达最后一个路口前
5 转完最后一个路口的返回药房
  1. 对应标志的控制
标志 控制
0 停车
1 发车并PID循线,遇到路口时获取Openart的信息来控制左右转
2 停车
3 原地旋转180度
4 PID循线,遇路口时按来时的方向反向转弯
5 加速并按PID循线直到丢线停车
  1. 发挥1部分

    车2理论上不需要识别,只需要记录车1的转弯来对应串口发送指令控制车2路线即可。

    核心代码:

1.通信部分以及控制指令

 #include "headfile.h"struct OPENMV  mv,art;char * p=mv.str;int i=0;
/*
函数:openmv_get_str()
变量说明:
UART1-与openmv通信
UART0-与openart通信
UART2-与车2通信
@mv.data:每次串口中断接收到的字符
@mv.str:将一次完整的数据储存到字符串,T,F,L为一次发送结尾标志,并分别对应T-路口,F-不是路口,L-丢线
@mv.err:将字符串偏差转化为浮点型的偏差
@mv.track:对应T,L,F,2-是路口,0-丢线,1-普通赛道
*/  void openmv_get_str(){if (uart_query(UART_1,&mv.data)){if (mv.data!='T' && mv.data!='F' && mv.data!='L' ){mv.str[i]=mv.data;i++;}else if (mv.data=='T'){mv.str[i]='\0';i=0;mv.err = atof(mv.str);memset(mv.str,0,sizeof(mv.str));mv.track=2;}else if (mv.data=='F'){mv.str[i]='\0';i=0;mv.err = atof(mv.str);memset(mv.str,0,sizeof(mv.str));mv.track=1;}else if (mv.data=='L'){mv.str[i]='\0';i=0;mv.err = atof(mv.str);memset(mv.str,0,sizeof(mv.str));if (!art.con_left && !art.con_right) mv.track=0;}}}
/*
控制指令函数——方向控制
函数:direc_control()
变量说明:
@art.con_left/right
状态1:(0秒~cnt.turn_sec秒)期间按固定占空比转弯
状态2:(cnt.turn_sec秒~cnt.turn_sec*2秒)期间按PID自然回线且不判断丢线(避免判断为到达病房)
@cnt.left/right:左转计时变量,1次中断5ms。
@cnt.turn_time:转弯固定差速持续的时间以及转弯后一段不判断丢线的时间。
@cnt.turn_num:转弯的次数(即遇到的路口数)
@flag.drug:药品的状态
@flag.str:是否发车
@flag.is_judge:发挥1的相关变量,即告诉车2已识别中端病房路口
@flag.send_direc:发挥1的相关变量,即告诉车2要转弯的方向
@flag.turn_memory[]:记下来时的方向,回去遇到路口时则从数组末端开始来控制方向
*/void direc_control(){if (art.con_left){cnt.left++;if (cnt.left>cnt.turn_time*1000/5){art.con_left=2;if (cnt.left>cnt.turn_time*2000/5){art.con_left=0;cnt.left=0;if (flag.drug<4&&flag.str){if (!flag.is_judge){flag.is_judge=1;flag.send_direc='Z';}flag.turn_memory[cnt.turn_num]=1;cnt.turn_num++;}else if(flag.drug>3){cnt.turn_num--;}}}}else if (art.con_right){cnt.right++;if (cnt.right>cnt.turn_second*1000/5){art.con_right=2;if (cnt.right>cnt.turn_second*2000/5){/**该if中代码某次控制中只执行一次(最后一次5ms中断进入)**/art.con_right=0;cnt.right=0;if (flag.drug<4&&flag.str){/*发挥1部分*/if (!flag.is_judge){flag.is_judge=1;flag.send_direc='X';}/**********/flag.turn_memory[cnt.turn_num]=2;cnt.turn_num++;}else if(flag.drug>3){cnt.turn_num--;}}}}}
/*
控制指令函数——赛道判别
函数:track_control()
变量说明:
@art.con_left/right:上面有介绍
@art.num_judge:识别到的数字
@flag.turn_memory[]:记下来时的方向,回去遇到路口时则从数组末端开始来控制方向
思路:分为三大种情况,1.如果识别的是1和2,则特殊处理。
*/void track_control(){if (mv.track==2 &&flag.drug!=5){/*识别到1或者2*/if (art.num_judge==1 || art.num_judge==2){if (art.num_judge==1 && flag.drug!=4 && flag.drug!=5)art.con_left=1;else if (art.num_judge==1 && flag.drug==4){art.con_right=1;flag.drug=5;}else if (art.num_judge==2 && flag.drug!=4 && flag.drug!=5)art.con_right=1;else if (art.num_judge==2 && flag.drug==4){art.con_left=1;flag.drug=5;}}/****************************//**识别到其他数字*****/else{/**如果是第一个路口(无两路口则直接下一个判断)**/else if(flag.drug==4 && cnt.turn_num == 2){if(flag.turn_memory[1]==1)art.con_right=1;else if (flag.turn_memory[1]==2)art.con_left=1;}/**如果是最后一个路口**/else if(flag.drug==4 && cnt.turn_num == 1 ){if(flag.turn_memory[0]==1)art.con_right=1;else if (flag.turn_memory[0]==2)art.con_left=1;flag.drug=5;}}}}
/*
函数:openart_get_str()
变量说明:
@art.data:接收的单个字符(由于三个串口底层都用较简单的阻塞式读取,所以除了openmv读取偏差外,其他两个串口只读取简单的单字符且仅对应某串口会发送此类字符,尽可能避免干扰openmv的通信)
@art.num_judge:识别的数字
*/  void openart_get_str(){if (uart_query(UART_0,&art.data)){if (art.data=='l'){art.con_left=1;}else if (art.data=='r'){art.con_right=1;}else if (art.data=='a'){art.num_judge=1;}else if (art.data=='b'){art.num_judge=2;}else if (art.data=='c'){art.num_judge=3;}else if (art.data=='d'){art.num_judge=4;}else if (art.data=='e'){art.num_judge=5;}else if (art.data=='f'){art.num_judge=6;}else if (art.data=='g'){art.num_judge=7;}else if (art.data=='h'){art.num_judge=8;}}}

2.电机控制

/*
函数:motor_control()
变量说明:
@enc.speed_contorl_out:速度环输出
@enc.dif:方向环输出差速
@enc.left_pwm/right:左右速度
@cnt.round_second:原地旋转持续时间
*/
void motor_control()
{if (flag.drug==5){enc.left_pwm=enc.speed_control_out+2000-enc.dif;//steer.angle即电机差速enc.right_pwm=enc.speed_control_out+2000+enc.dif;}else{enc.left_pwm=enc.speed_control_out+0-enc.dif;//steer.angle即电机差速enc.right_pwm=enc.speed_control_out+0+enc.dif;}if (!flag.man_swi){if (flag.drug==3){cnt.turn_round++;if (cnt.turn_round>cnt.round_second*1000/5){cnt.turn_round=0;flag.drug=4;}motor(3000);motor2(3000);}else if(art.con_left==1){motor(1000);motor3(4000);}else if (art.con_right==1){motor(4000);motor3(1000);}else if (mv.track==0 && (flag.drug==5 || flag.drug==2) ){motor(0);motor3(0);motor2(0);motor1(0);}else{if (enc.left_pwm>0){motor(enc.left_pwm);motor1(0);}else{motor(0);motor1(-enc.left_pwm);}if (enc.right_pwm>0){motor2(0);motor3(enc.right_pwm*1.2);}else{motor2(-enc.right_pwm*1.2);motor3(0);}}}}

3.中断

IFX_INTERRUPT(cc61_pit_ch0_isr, 0, CCU6_1_CH0_ISR_PRIORITY)
{enableInterrupts();//开启中断嵌套PIT_CLEAR_FLAG(CCU6_1, PIT_CH0);//自动循迹if (!flag.man_swi){/***************PID*************//*向车2发送信号*/Send_to_car2();/*外设处理*/reeds_get();//检测是否装载着药品       if_put_drug();//是否放上药物/*亮红灯——病房丢线*/if (flag.drug==1 && mv.track==0){flag.drug=2;flag.led=1;}if_get_drug();//是否取下药物/*亮绿灯——药房丢线*/if (flag.drug==5 && mv.track==0)flag.led=2;led_control();//提示灯控制/*摄像头处理*/openmv_get_str();if (!art.left_control && !art.right_control){if (flag.drug<3) openart_get_str();track_control()                    }direc_control();PID_steer_trace();/**准备、发车**/if (flag.str){if (flag.mot_con) motor_control();
//                  if (flag.ste_con) steer_control();四轮车不好原地转弯,搭车还浪费好久时间}/**计数**/Count();}/************人工测试***********/else{openmv_get_str();if (flag.str){if (flag.mot_con) motor_control();
//              if (flag.ste_con) steer_control();}/**计数**/Count();}}

4.车2代码
核心思路:笔者是让车2在车1到达中端病房后前往与车1同侧的近端病房,记录车1在路口时转的方向,车2只需按这个方向在遇到路口时先转1次即到达车1的同侧近端病房,然后等待车1返回到达药房发送启动指令,车2原地旋转,再在遇到路口时转2次(共俩路口)即可到达车1抵达的病房。

/**获取车1指令**/
void car2_get_str()
{if (uart_query(UART_2,&getcar1.data)){/*得知车1的方向是左转*/if (getcar1.data=='Z' ){flag.turn_direc=1;flag.str=1;}/*得知车1的方向是右转*/else if (getcar1.data=='X'){flag.turn_direc=2;flag.str=1;}/*接收到原地旋转指令*/else if (getcar1.data=='S'){flag.get_turn=1;}}}
/**方向控制**/
void track_control()
{if (mv.track==2 &&flag.drug!=5){if (flag.turn_direc==1){art.con_left=1;}else if (flag.turn_direc==2){art.con_right=1;}}
}
void direc_control()
{//同车1
}
void motor_control()
{//同车1
}
/***中断部分***/
IFX_INTERRUPT(cc61_pit_ch0_isr, 0, CCU6_1_CH0_ISR_PRIORITY)
{enableInterrupts();//开启中断嵌套PIT_CLEAR_FLAG(CCU6_1, PIT_CH0);//自动循迹--AI/PIDif (!flag.man_swi){/***************PID*************//*接收主车通信*/car2_get_str();/*外设处理*/led_control();flag.drug=0;/*摄像头处理*/openmv_get_str();if (cnt.left==0 && cnt.right==0){track_control();}direc_control();PID_steer_trace();/**准备、发车**/if (flag.str){if (flag.mot_con) motor_control();}/**计数**/Count();}}

可优化的点

  • 数字移动过程中识别帧率过低导致识别不准:
    很有效的解决方法,只需在控制中加入判断到十字停车2s进入识别状态,识别后退出识别状态并控制转弯。
    可惜:但当时时间太赶,身体素质不好,熬夜人弄麻了,觉得这样整体时间会超时,其实只要控制电机多提速就好了,而且测评时专家没强调时间。这真的是我觉得最遗憾的,当时人麻了,没好好思考,也是技术太菜,一言难尽。
  • 识别过于依赖光线亮暗
    事实上我对这点还是有所疑问,因为上面那个问题才是真正主要因素,静态时对数字的识别其实准确率有90%以上:(那天识别手持数字几乎没出错过)。当然换了场地后的影响也挺大(早晨8点的亮度也跟下午不太一样),确实该写个滤光算法使摄像头适应各种场景内的均匀光,太阳的直射光问了调摄像头的同学其实也不太好解决,只能拉窗帘了。
    可惜:在这点耽误了太多时间,控制与通信大概就写了4小时左右,识别一直效果不好,却没想到是思路大方向错了,与其不断改进训练集提高亮暗识别度以及降低模型复杂度提高帧率,不如控制上做出大的改变在十字处停下识别才是最核心的,后来看有openmv用模板匹配的同学这样识别率也不低。

尾声

  • 当时时间紧没怎么拍照留念,之后拆箱了再优化优化,放上模型文件以及训练过程、完整控制工程、做点效果图、视频等放博客上回念了。这可能是大学最后一次参加比赛了,就结果来看还是蛮遗憾的。
    个人是只负责视觉和控制的软件部分,硬件上有机会问问队友再放了。 之后也打算整理好每次做项目的代码放到网盘、博客,多多复盘,多多整理,不断成长吧,未来可期!

交大大佬的识别(相见恨晚):
https://zhuanlan.zhihu.com/p/391216590
openmv中文手册
https://docs.singtown.com/micropython/zh/latest/openmvcam/index.html
openart开源教程:
http://mp.weixin.qq.com/s?__biz=MzAxMjQxNjEyMw==&mid=2247487040&idx=1&sn=4d47976b398b3c92811a29356ada34b9&chksm=9bb36b54acc4e242edf935986c97ee1f6363dffc86678faf64c65903476f8c27504e1a392120&mpshare=1&scene=23&srcid=1203gAB1ALoBvqjYxJd7qp5c&sharer_sharetime=1638461358289&sharer_shareid=b8ab17ea4161b8f0d5c69ddb66ea7563#rd

规则


【AI视觉】智能送药小车——1.复盘及核心代码相关推荐

  1. 2021年全国大学生电子设计大赛F题——智能送药小车,全方位解决方案+程序代码(详细注释)山东赛区国奖

    目录 1.赛题及硬件方案分析: 2.用到的主要器件清单: 3.各部分思路及代码实现 (1).小车舵机.马达驱动 (2).蓝牙通信 (3).单片机与OpenMV的串口通信 (4).单片机与OpenMV的 ...

  2. 2021电赛F题(智能送药小车)参赛总结【视觉部分】

    2021电赛F题(智能送药小车)参赛总结[视觉部分] 前言 在2021年全国大学生电子设计竞赛中,我们小组做的是F题(智能送药小车).我在小组中主要负责小车视觉功能的实现,所以在本篇参赛总结中只会涉及 ...

  3. 做个全国一等奖的小车,其实不难(F题:智能送药小车方案分享)

    01  前 言 大家好,我是张巧龙,今天给大家带来关于21年F题的分享:智能送药小车,出了这个题目之后,咋一看,好像比较简单. 不过大家慢慢做,越往后做越发现,坑越来越多. 第一个问题:数字识别率不高 ...

  4. 2021电赛F题智能送药小车方案分析(openMV数字识别,红线循迹,STM32HAL库freeRTOS,串级PID快速学习,小车自动返回)

    2021全国大学生电子设计竞赛F题智能送药小车 前提:本篇文章重在分享自己的心得与感悟,我们把最重要的部分,摄像头循迹,摄像头数字识别问题都解决了,有两种方案一种是openARTmini摄像头进行数字 ...

  5. 2021电赛国一智能送药小车(F题)设计报告

    2021电赛国一智能送药小车(F题)设计报告 [写在前面的话] 电赛是一个很奇妙的过程,可能有些人觉得电赛的门槛太高,那便意味着,当你决定要参加电赛的那一刻起,这一段路.这些日子就注定不会太轻松: 我 ...

  6. 全国一等奖,F题:智能送药小车。

    大家好,我是张巧龙,今天给大家继续带来电赛F题的分享:智能送药小车. 今天这个车也获得了全国一等奖,这次获奖队伍和上次获奖队伍,都是我赛前指导的学生做的. 上次的文章链接:做个全国一等奖的小车,其实不 ...

  7. 智能送药小车(一)——K210巡线

    智能送药小车 K210 颜色识别 K210 找出最大色块 K210 颜色识别 这部分基本就是例程上的内容,添加了一点注释.关于相关的运用到的函数解释,可以参考K210颜色识别. import sens ...

  8. 2021电赛F题-智能送药小车-国一

    2021电赛F题-智能送药小车-国一 B站视频链接:https://www.bilibili.com/video/BV1u44y1e7qk/ (这大概是b站第一个双车视频吧,嘿嘿

  9. (不眠者①队)国电-F题:智能送药小车,广东赛区一等奖,推国赛,开源(代码+设计方案)

    国电 -- F题: 智能送药小车,所有源码暂时开源 目录 国电 -- F题: 智能送药小车,所有源码暂时开源 前言 一.题目 二.硬件部分 材料清单: 材料实物图: 设计方案: 三.代码部分 数字识别 ...

最新文章

  1. MySQL8索引篇:性能提升了100%!!
  2. 一文看懂Tomcat、Nginx和Apache的区别
  3. Linux学习之系统编程篇:mmap 内存映射区
  4. 【Java程序设计】输入输出
  5. mysql 0000-00-00无效_mysql0000-00-00日期异常及解决方法
  6. 用python和OpenCV进行动态物体检测
  7. 麒麟系统编译网卡驱动
  8. hdu5336XYZ and Drops
  9. 阿里高管的思考方式真正厉害在哪?内部员工7000字深度干货
  10. android系统 vender添加自定义的预编译的应用程序
  11. 高德地图 poi 搜索
  12. python配置文件
  13. Dagger2 进阶
  14. SaaS、云计算、软件:危险的“三角恋”
  15. iOS AVAudioEngine使用教程
  16. python之算数运算
  17. 培训效果评估反馈调查/企业问卷调查系统
  18. 【IEEE_SV-7.10】队列Queues
  19. 亚马逊listing文案怎么写?有哪些技巧?
  20. 计算机应用基础第四版必考点,2018计算机应用基础考试知识点复习考点归纳总结...

热门文章

  1. 树莓派4b学习笔记一:树莓派4B开箱简单配置(远程工具+opencv+pytorch1.3)
  2. 摩拜进军澳大利亚;刘强东怒斥行业潜规则;博通计划1300亿美元收购高通丨价值早报
  3. The Forty-Year Programmer
  4. 阻塞语句与非阻塞语句
  5. PNAS:节食可调节年轻人脑网络的稳定性
  6. JavaScript的onkeypress键盘事件
  7. openwrt挂载拓实n95 n815大功率usb无线网卡
  8. 微信小程序登录获取不到头像和昵称解决办法!
  9. 使用python+selenium获取网易云指定歌曲评论
  10. sphinx-1.3.0扩展在pPHP 7.0.7版本编译不通过