前言
感谢 华北电力大学(保定)的Y1ZzLu佬 开源的固件!
感谢 华北电力大学(保定)的Y1ZzLu佬 开源的固件!!
感谢 华北电力大学(保定)的Y1ZzLu佬 开源的固件!!!
这篇文章用于提供自己对修改openart固件来解决无框卡片寻找问题的一个方案,由于本人能力不足,并且第一次写博客,所以诸多纰漏还请读者见谅。
一、为什么选择修改openart固件?
原始算法帧率过低
最开始接触智能视觉的视觉部分的时候,就是先打算解决无框卡片的识别问题,因为我发现用openart的find_blob函数很难去识别出无框卡片,并且有框卡片的识别的帧率不是很高,印象中应该就十几帧或者更低。 具体反应在控制上就是控制的时间周期很长,因为我以前调过四轮,所以很清楚帧率对于控制的重要性,如果能提高帧率,无疑能大大减轻控制的压力。后面我试着采用yolo去跑目标检测用于校正,但是受限于模型精度,不管怎么跑都跑不出精度达到我们结构的精度要求。后来在机缘巧合下找到了智能视觉组的开源项目,在里面找到了openart的开源固件。在基于华北电力大学的开源固件上,修改加入了适合今年场景的一些算法,使得能够用于今年的赛题。在修改后,帧率最高在24帧左右,最低18帧,基本可以满足校正的需求。
二、修改的一些思路和源代码
1.总体思路
因为原来开源固件的矩形检测的帧率和框架都完全满足要求,但是这次的卡片背景并不是纯蓝色,背景较为复杂,所以需要对原有的固件进行一些修改,使得能够满足要求。
我首先想到的就是把赛道给分割出来,都作为蓝色处理,在实现的过程中发现内存有点不够用,在逐渐理解Y1ZzLu佬的算法后,想到只要把与卡片相交的黑胶的那条线给找出来,用于分割卡片和赛道,就能成功的找到矩形。所以我的算法重点就是放在了怎么去寻找这一条线上。
2.具体实现和改进
(1).找黑胶
这里模仿Y1ZzLu佬,采用了寻找最大连通域的算法,用于提取出黑胶,并且用于后续的处理。
staticinlineintBFS_color(uint8_t*im,uint16_t x,uint16_t y,uint16_t*X,uint16_t*Y, bool *st,uint8_t thresh,image_t*ptr){uint16_t area =0;
st[y * IMG_W + x]=true;int16_t arr[4][2]={{0,1},{-1,0},{0,-1},{1,0}};//四领域int16_t queuehead =0, queuetail =0;queuepush(X, Y,&queuetail, x, y, my_queuesize);point_t*pnts =fb_alloc(sizeof(point_t)* pntsize, FB_ALLOC_NO_HINT);uint16_t maxx = x, minx = x, maxy = y, miny = y;//定义检索的范围while(queue_size(queuehead, queuetail, my_queuesize)){point_t t =queuefront(X, Y, queuehead);queuepop(&queuehead, my_queuesize);
area++;for(uint8_t i =0; i <4; i++){int16_t nextx = t.x + arr[i][0], nexty = t.y + arr[i][1];if(im[nexty * IMG_W + nextx]!=thresh){break;}if(nexty <4|| nextx <4|| nexty > IMG_H -4|| nextx > IMG_W -4)continue;//索引超过边界if(im[nexty * IMG_W + nextx]==thresh&& st[nexty * IMG_W + nextx]==false)//把满足条件的点加入数组{queuepush(X, Y,&queuetail, nextx, nexty, my_queuesize);
pnts[my_queuesize].x = nextx;
pnts[my_queuesize].y = nexty;
maxx =MAX(maxx, nextx);
minx =MIN(minx, nextx);
maxy =MAX(maxy, nexty);
miny =MIN(miny, nexty);
st[nexty * IMG_W + nextx]=true;}}}//mp_printf(&mp_plat_print, "queuetail = %d\n", queuetail);uint16_t dx = maxx - minx, dy = maxy - miny;if(dx <100&& dy <100){fb_free();return0;}elseif(my_queuesize <200){fb_free();return0;}else{fb_free();//mp_printf(&mp_plat_print, "dx = %d dy = %d\n", dx,dy);return area;}}for(int degree =0; degree <360; degree +=30){float x =160, y =120;float rad = degree * PI /180;float dx =cos(rad), dy =sin(rad);int i, j;for(;;){
x += dx;
y += dy;
i =(int)y;
j =(int)x;if(i <4|| j <4|| i > IMG_H -4|| j > IMG_W -4)// 图片的边缘不检索break;if(Img[320* j + i]== thresh && my_st[320* j + i]== false){// 满足颜色条件并且没有被访问过uint16_t area =BFS_color(Img, i, j, my_queueX, my_queueY, my_st, thresh,ptr);if(area>2000){
area_count++;}//mp_printf(&mp_plat_print, "area_count = %d\n", area_count);if(area > maxArea){
maxArea = area;// 清空之前的最大连通域数据
maxQueueHead =0;
maxQueueTail =0;// 复制当前连通域数据到最大连通域队列memcpy(max_queueX, my_queueX, maxArea *sizeof(uint16_t));memcpy(max_queueY, my_queueY, maxArea *sizeof(uint16_t));
maxQueueTail = maxArea;// 更新最大队列尾部}// 重置当前队列尾部,准备下一轮搜索}}}
手头没有其他截图,拿一张被数据线截断的演示一下效果。
(2).找线
找线有很多方法和思路,这里仅提供我试过的可行的方案。我认为提供的方案在稳定性上是还可以的,但是为了一些情况舍弃了速度,并且能改进的地方还有很多很多,仅作为一个能使用的方案供参考。
先放代码
*******************************判断一个点的下面是不是与白色相邻->用于判断是不是想要的线内的点****************************
bool pix_around_white(image_t*ptr,int x,int y){uint8_t h, s, v;
bool white_flag=0;
bool other_flag=0;for(int i=x;i<=x;i++){for(int j=y+1;j<=y+5;j++){uint16_t pix =IMAGE_GET_RGB565_PIXEL_FAST(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, j), i);uint8_t R =COLOR_RGB565_TO_R8(pix), G =COLOR_RGB565_TO_G8(pix), B =COLOR_RGB565_TO_B8(pix);get_hsv(pix,&h,&s,&v);if(!(h >=100&& h <=124)&&!((B <50&& G <50&& R <50)||((B <100&& G <100&& R <100)&&my_abs(B,G)<15&&my_abs(B,R)<15&&my_abs(R,G)<15)&&(((B >160&& G >160&& R >160&&my_abs(B, G)<25&&my_abs(B, R)<25&&my_abs(R, G)<25)||(B >100&& G >100&& R >100&&my_abs(B,G)<15&&my_abs(B,R)<15&&my_abs(R,G)<15)||(B >190&& G >190&& R >190)))))//白色{
white_flag=1;break;//只要此黑色下面五个点有一个为白色便判断为有白色}else{
white_flag=0;}}}if(white_flag==0){return true;}else{return false;}}**********************************具体找线代码,在连通域的基础上做处理,并且加入曲线拟合****************************for(int i =0; i <=maxArea; i++){if(max_Y[max_queueX[i]]<max_queueY[i]){
max_Y[max_queueX[i]]=max_queueY[i];}//找到连通域每一列最下面的黑色}for(int i =21; i <299; i++){if(max_Y[i]==0){
error_count++;}//如果没被赋值,说明这一列没有想要的点elseif((max_Y[i]!=0&&pix_around_white(ptr,i,max_Y[i]))==true||max_Y[i]>220){
X[count]= i;
Y[count]= max_Y[i];
count++;}//满足被赋值(说明该列有黑胶),并且是固定赛道的黑胶}for(int i =0;i< count;i++){if(threshold%10==2){imlib_set_pixel(ptr,X[i],Y[i],255);}}if(error_count >0&&count >3)// 有断点用曲线拟合(一般不会出现这种情况,但是加入可以提升鲁棒性){double sumX =0, sumX2 =0, sumX3 =0, sumX4 =0, sumX5 =0, sumX6 =0;double sumY =0, sumXY =0, sumX2Y =0;// 计算所需的各项和for(int i =0; i < count; i++){double xi = X[i];double yi = Y[i];double xi2 = xi * xi;double xi3 = xi2 * xi;double xi4 = xi3 * xi;double xi5 = xi4 * xi;double xi6 = xi5 * xi;
sumX += xi;
sumX2 += xi2;
sumX3 += xi3;
sumX4 += xi4;
sumX5 += xi5;
sumX6 += xi6;
sumY += yi;
sumXY += xi * yi;
sumX2Y += xi2 * yi;}// 构建矩阵 A 和向量 Bdouble A[3][3]={{sumX4, sumX3, sumX2},{sumX3, sumX2, sumX},{sumX2, sumX, count}};double B[3]={sumX2Y, sumXY, sumY};// 使用高斯消元法求解线性方程组gaussian_elimination(A, B,3);
a = B[0], b = B[1], c = B[2];// 画线部分for(int x =0; x < IMG_W; x++){int y =(int)(a * x * x + b * x + c);// 计算二次曲线上每个 x 对应的 yif(y >=0&& y < IMG_H){if(threshold%10==2){imlib_set_pixel(ptr,x,y,100);}// 使用绿色绘制点}}//mp_printf(&mp_plat_print, "Fitted quadratic curve: y = %.5fx^2 + %.2fx + %.2f\n", a, b, c);}else{//mp_printf(&mp_plat_print, "Not enough points to fit a quadratic curve.\n");}
具体的就是在找到了一个满足连通域条件的基础上,去找到这条线。因为黑胶的宽度是基本一致的,并且下面的一条与白赛道相接的线图像会比较稳定,因为只有白色的赛道,所以我选择去找下面的这条线,然后再往上平移。找这条线的方法很多,可以找最长的黑白相接连通的线,也可以像我这样比较简单的就是找每一列最下面的黑色。因为我们的车是一直都在赛道上的,所以图像方面比较稳定,可以确保这样找到的就是想要的点。如果方案比较复杂,这种算法就不适用了,需要找别的方法。然后这样找到了点,一般情况都是直接构成能满足要求的线,但是在受反光和一些奇奇怪怪的影响下,可能会导致连通域的寻找和黑线上的点的寻找出问题,所以我加入了二次曲线拟合,可以提供图像的鲁棒性。
(3).找矩形
找矩形的主要部分直接使用了Y1ZzLu佬的。当卡片是黑色的时候,会与黑胶相连接。我做的是把找到线后的图像进行二次处理,我把找到的连通域中,在这条线上面的黑色作为有效数据处理,把下面的则就当做蓝色,不做处理。
for(int width=threshold/10;width<threshold/10+10;width++){if(error_count==0){for(int i =0; i < maxArea; i++){if(max_queueY[i]<=max_Y[max_queueX[i]]-width)//在黑胶上的黑色部分{*(Img +320* max_queueY[i]+ max_queueX[i])=0;//imlib_set_pixel(ptr,max_queueX[i],max_queueY[i],100);}//把线上并且是最大连通域内的设置为0,所以如果卡片没和黑胶联通,则不用担心else{*(Img +320* max_queueY[i]+ max_queueX[i])=255;}//当做黑胶不做处理}}elseif(error_count>0&&count>3){for(int i =0; i < maxArea; i++){if(max_queueY[i]<=a * max_queueX[i]* max_queueX[i]+ b * max_queueX[i]+ c-width){*(Img +320* max_queueY[i]+ max_queueX[i])=0;//imlib_set_pixel(ptr,max_queueX[i],max_queueY[i],100);}//把线上并且是最大连通域内的设置为0,所以如果卡片没和黑胶联通,则不用担心else{*(Img +320* max_queueY[i]+ max_queueX[i])=255;}//当做黑胶不做处理}}//mp_printf(&mp_plat_print, "maxarea = %d\n", maxArea);structquad Rect;list_init(out,sizeof(find_rects_list_lnk_data_t));if(Find_Rect(&im,5,&Rect,quality)){uint16_t x, y, w, h, minx = IMG_W -1, maxx =0, miny = IMG_H -1, maxy =0, min_sum = IMG_W + IMG_H;uint8_t idx =0;uint16_t distance=0;find_rects_list_lnk_data_t lnk_data;for(uint8_t i =0; i <4; i++){
minx =MIN(minx,(uint16_t)Rect.p[i][0]);
maxx =MAX(maxx,(uint16_t)Rect.p[i][0]);
miny =MIN(miny,(uint16_t)Rect.p[i][1]);
maxy =MAX(maxy,(uint16_t)Rect.p[i][1]);if(Rect.p[i][0]+ Rect.p[i][1]< min_sum){
min_sum = Rect.p[i][0]+ Rect.p[i][1];
idx = i;}}
x = minx, y = miny, w = maxx - minx, h = maxy - miny;
lnk_data.corners[3].x = Rect.p[idx][0];
lnk_data.corners[3].y = Rect.p[idx][1];
lnk_data.corners[2].x = Rect.p[(idx +3)%4][0];
lnk_data.corners[2].y = Rect.p[(idx +3)%4][1];
lnk_data.corners[1].x = Rect.p[(idx +2)%4][0];
lnk_data.corners[1].y = Rect.p[(idx +2)%4][1];
lnk_data.corners[0].x = Rect.p[(idx +1)%4][0];
lnk_data.corners[0].y = Rect.p[(idx +1)%4][1];// if (check_yellow(ptr, lnk_data.corners[3], lnk_data.corners[1], quality) && check_yellow(ptr, lnk_data.corners[1], lnk_data.corners[3], quality) && check_yellow(ptr, lnk_data.corners[2], lnk_data.corners[0], quality) && check_yellow(ptr, lnk_data.corners[0], lnk_data.corners[2], quality))// mp_printf(&mp_plat_print, "kuang!\n");rectangle_init(&(lnk_data.rect), x, y, w, h);if(error_count==0){
distance=(int)(max_Y[x+h/2]-(y+h/2));}elseif(error_count>0&&count>3){
distance=(int)(a*(x+h/2)*(x+h/2)+b*(x+h/2)+c-(y+h/2));}//mp_printf(&mp_plat_print, "dis= %d\n", distance);if(distance>80)//离赛道较远的卡片忽略(防止环岛卡片离道路过近){fb_free();fb_free();fb_free();fb_free();fb_free();fb_free();break;}list_push_back(out,&lnk_data);fb_free();fb_free();fb_free();fb_free();fb_free();fb_free();break;}
这里有一个for循环,是为了解决黑胶宽度不均匀的问题,因为我是在找到下面的线的基础上向上平移指定距离,所以鲁棒性不高,加入该段代码牺牲了算法速度,但是提高了一定的鲁棒性。但是我觉得这段代码写的还是很粗略,改进的空间很大。
(4).art与rt1064的通讯
import pyb
import sensor, image, time, math
import os, tf
from machine import UART
######此为老车右sd卡main函数
pi=3.1415926
NET_SIZE =80#网络大小
BORDER_WIDTH =(NET_SIZE +9)//10
CROP_SIZE = NET_SIZE + BORDER_WIDTH *2
pic_x=160#标准的中心坐标
pic_y=120
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(800)
sensor.skip_frames(time =20)
sensor.set_auto_gain(False)# must turn this off to prevent image washout...
sensor.set_auto_whitebal(False,(0,0,0))# must turn this off to prevent image washout...
sensor.set_auto_exposure(False)
uart = UART(1, baudrate=115200)# 初始化串口 波特率设置为115200 TX是B12 RX是B13
do_classify=False
do_line =False
rect_flag =False
model_select=False#255是蓝色b阈值,看场上情况调整(已经废除不用),这个主要是和rt1064通信的部分,用来区分环岛和常规元素
rect_select=12550#检查是否为12550
clock = time.clock()
net_path1 ="kapian.tflite"
labels1 =[line.rstrip()for line inopen("/sd/lables_kapian.txt")]
net_path2 ="zimu.tflite"
labels2 =[line.rstrip()for line inopen("/sd/lables_zimu.txt")]
net1 = tf.load(net_path1, load_to_fb=True)
net2 = tf.load(net_path2, load_to_fb=True)#20是黑胶宽度,0,1,2调试选项,0的话是正常跑的时候,1是显示图像中的蓝色白色和黑色,一定要确保符合实际,有部分噪点也不影响,一般不会有,2是显示提取的线,一般换场地跑前会全检查一下
threshold=200#检查是否为200
result_index=-1
count=0defcheck_a_b(a,b,k):if(a-b<=k and a-b>=-k):return1else:return0#计算两点角度值defget_point_point_angle(p0,p1):
x1,y1=p0
x2,y2=p1
if(y2!=y1):
theta=math.atan((x2-x1)/(y2-y1))
theta=theta/pi*180returnint(theta)return0#矩形四点获取距离defget_distance(p0,p1,p2,p3):
x1,y1=p0
x2,y2=p1
x3,y3=p2
x4,y4=p3
dis1=(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)#得到距离
dis2=(x2-x3)*(x2-x3)+(y2-y3)*(y2-y3)
dis3=(x3-x4)*(x3-x4)+(y3-y4)*(y3-y4)
dis4=(x4-x1)*(x4-x1)+(y4-y1)*(y4-y1)
dis1=math.sqrt(dis1)
dis2=math.sqrt(dis2)
dis3=math.sqrt(dis3)
dis4=math.sqrt(dis4)returnint(dis1),int(dis2),int(dis3),int(dis4)#两点长度计算defget_point_point_distance(p0, p1):
line_s_x, line_s_y=p0
line_e_x, line_e_y=p1
line_long = math.sqrt(pow(line_s_x - line_e_x,2)+pow(line_s_y - line_e_y,2))return line_long
#点到直线距离公式defget_point_line_distance(point, p0, p1):
point_x = point[0]
point_y = point[1]
line_s_x, line_s_y=p0
line_e_x, line_e_y=p1
#若直线与y轴平行,则距离为点的x坐标与直线上任意一点的x坐标差值的绝对值if line_e_x - line_s_x ==0:return math.fabs(point_x - line_s_x)#若直线与x轴平行,则距离为点的y坐标与直线上任意一点的y坐标差值的绝对值elif line_e_y - line_s_y ==0:return math.fabs(point_y - line_s_y)else:#斜率
k =(line_e_y - line_s_y)/(line_e_x - line_s_x)#截距
b = line_s_y - k * line_s_x
#带入公式得到距离dis
dis = math.fabs(k * point_x - point_y + b)/ math.pow(k * k +1,0.5)return dis
# 这个示例演示如何加载tflite模型并运行# 这个示例演示如何加载tflite模型并运行# 这个示例演示如何加载tflite模型并运行while(True):
clock.tick()
msg=uart.read()#print(msg)
rect_flag=False
do_classify =b"\xf4"in msg or do_classify
if(b"\xf5"in msg):
model_select=not model_select
if(b"\xf5"in msg):
rect_select=(int)(257-rect_select/10000)*10000+rect_select%10000;if(b"\xd3"in msg):
rect_select =(int)(1- rect_select %10)+(int)(rect_select /10)*10;
img = sensor.snapshot()for r in img.find_rects(threshold = threshold,quality = rect_select):# 在图像中搜索矩形
rect_flag=True
img_x=(int)((r.rect()[0]+r.rect()[2]/2)/2)# 图像中心的x值
img_y=(int)((r.rect()[1]+r.rect()[3]/2)/2)# 图像中心的y值
p0, p1, p2, p3 = r.corners()
dis1, dis2, dis3, dis4 = get_distance(p0, p1, p2, p3)
blob_angle = get_point_point_angle(p1, p2)
x_error = img_x - pic_x
y_error = img_y - pic_y
#print("img_x:" + str(r.rect()[0]) + ",img_y:" + str(r.rect()[3])+","+str(blob_angle))if(x_error <0):
x_flag =0# 正负标志位
x_error =-x_error
else:
x_flag =1if(y_error <0):
y_flag =0# 正负标志位
y_error =-y_error
else:
y_flag =1
uart_array1 =bytearray([0XE1,0xE2,x_error,y_error,x_flag,y_flag,blob_angle,0XE3])
uart.write(uart_array1)if do_classify:
corners = r.corners()
img.rotation_corr(x_translation=CROP_SIZE, y_translation=CROP_SIZE, corners=corners)if model_select:
obj=net2.classify(img,roi=(BORDER_WIDTH, BORDER_WIDTH, NET_SIZE, NET_SIZE))[0]
res=obj.output()
m=max(res)
result_index=labels2[res.index(m)]
uart_array2 =bytearray([0XBF,0XBF])#没有识别出任何东西if result_index=='1':#1
uart_array2 =bytearray([0XBF,0xB0])if result_index =='2':#2
uart_array2 =bytearray([0XBF,0xB1])if result_index =='3':#3
uart_array2 =bytearray([0XBF,0xB2])if result_index =='A':#A
uart_array2 =bytearray([0XBF,0xC0])if result_index =='B':#B
uart_array2 =bytearray([0XBF,0xC1])if result_index =='C':#C
uart_array2 =bytearray([0XBF,0xC2])if result_index =='D':#D
uart_array2 =bytearray([0XBF,0xC3])if result_index =='E':#E
uart_array2 =bytearray([0XBF,0xC4])if result_index =='F':#F
uart_array2 =bytearray([0XBF,0xC5])if result_index =='G':#G
uart_array2 =bytearray([0XBF,0xC6])if result_index =='H':#H
uart_array2 =bytearray([0XBF,0xC7])if result_index =='I':#I
uart_array2 =bytearray([0XBF,0xC8])if result_index =='J':#J
uart_array2 =bytearray([0XBF,0xC9])if result_index =='K':#K
uart_array2 =bytearray([0XBF,0xCA])if result_index =='L':#L
uart_array2 =bytearray([0XBF,0xCB])if result_index =='M':#M
uart_array2 =bytearray([0XBF,0xCC])if result_index =='N':#N
uart_array2 =bytearray([0XBF,0xCD])if result_index =='O':#O
uart_array2 =bytearray([0XBF,0xCE])#print("1")#print("%s: %f" % (result_index, m))
uart.write(uart_array2)
do_classify=Falseelse:
obj=net1.classify(img,roi=(BORDER_WIDTH, BORDER_WIDTH, NET_SIZE, NET_SIZE))[0]
res=obj.output()
m=max(res)
result_index=labels1[res.index(m)]
uart_array2 =bytearray([0XBF,0XBF])#没有识别出任何东西#中0if result_index=='firearms':#枪
uart_array2 =bytearray([0XBF,0xA0])if result_index =='explosive':#炸弹
uart_array2 =bytearray([0XBF,0xA1])if result_index =='dagger':#匕首
uart_array2 =bytearray([0XBF,0xA2])#上1if result_index =='spontoon':#警棍
uart_array2 =bytearray([0XBF,0xA3])if result_index =='fire_axe':#消防斧
uart_array2 =bytearray([0XBF,0xA4])if result_index =='first_aid_kit':#急救包
uart_array2 =bytearray([0XBF,0xA5])#下2if result_index =='flashlight':#手电筒
uart_array2 =bytearray([0XBF,0xA6])if result_index =='intercom':#bb机
uart_array2 =bytearray([0XBF,0xA7])if result_index =='bulletproof_vest':#防弹衣
uart_array2 =bytearray([0XBF,0xA8])#左3if result_index =='telescope':#望远镜
uart_array2 =bytearray([0XBF,0xA9])if result_index =='helmet':#头盔
uart_array2 =bytearray([0XBF,0xAA])if result_index =='fire_engine':#救火车
uart_array2 =bytearray([0XBF,0xAB])#右4if result_index =='ambulance':#救护车
uart_array2 =bytearray([0XBF,0xAC])if result_index =='armoured_car':#装甲车
uart_array2 =bytearray([0XBF,0xAD])if result_index =='motorcycle':#摩托车
uart_array2 =bytearray([0XBF,0xAE])#print("2")#print("%s: %f" % (result_index, m))
uart.write(uart_array2)
do_classify=False
img.draw_rectangle(r.rect(), color =(255,255,255))
总结
工程已经上传github,地址https://github.com/gameworld11/openart.git,有问题可以留言,在我能力范围的我会尽力解答。
版权归原作者 S p i k e- 所有, 如有侵权,请联系我们删除。