2020.7.20
由于数据集的局限性以及实际环境的差异性及不可控性,我们不能一直得到像下图左图一样的理想模型,对应的话拟合出来的线肯定是需要进行筛选的.
一开始我采取的是简单一次拟合,截取了这部分代码如下:
检测轮廓,接受的参数为二值图,即黑白的(不是灰度图):
contours, hierarchy = cv2.findContours(mask_image,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#绘制轮廓
cv2.drawContours(image,contours,-1,(0,0,255),1)
col,row = mask_image.shape[:2]#列cols:X (width);行rows:Y (height)
#输出满足条件 (即非0) 元素的坐标
y,x=np.where(mask_image==255)
if len(x) > 1:#x长度大于1
tmp_f = np.polyfit(x,y,1)#用一次多项式拟合
tmp_p = np.poly1d(tmp_f)#得到多项式系数,按照阶数从高到低排列
print(tmp_p) #显示多项式
print(tmp_p.coefficients) #显示多项式系数
center = (row/2,col/2)
new_p = [(px,int(tmp_p(px))) for px in range(row)]
k1 = tmp_p.coefficients[0]
b1 = tmp_p.coefficients[1]
k2 = -1/k1
b2 = center[1] - k2*center[0]
crosspx = (b2-b1)/(k1-k2)
crosspy = k1*crosspx + b1
distan = np.sqrt((center[0]-crosspx)**2+(center[1]-crosspy)**2)
crossp = (int(crosspx),int(crosspy))
center = (int(row/2),int(col/2))
#v_line = [(px,int(k2*px+b2)) for px in range(row)]
#print(new_p)
for i in range(len(new_p)-1):#画红色多边形部分
cv2.line(image,new_p[i],new_p[i+1],[0,255,0],1)
cv2.line(image,center,crossp,[0,0,255],1)
theta = np.arctan(k1)/np.pi*180#反解斜率角
print(theta)
cv2.putText(image,str(theta),(10,10),1,1,[0,255,0],1)
cv2.putText(image,str(distan),(10,30),1,1,[0,255,0],1)
我们会发现只能拟合出单边的轨道图线,我有想过只用单边的线进行之后对硬件的控制,但拟合到弯道时会出现各种bug.
解决方法的话我也有设想:只能拟合单边无非是拟合了点最多的那一条线,那我可以想办法把视频分割成左右两半,分别拟合然后图像重叠;第二种思路的话,是提取模型中最大的两个多边形(自然是左右轨迹线)进行拟合.
2020.7.21
由于我作为一个初学者,学长建议我直接调用Hough变换来实现拟合,Hough变换的缺陷是计算量较大,所以在实际控制硬件时效果可能没那么好,今天主要学习了下Hough变换的原理以及函数变量,并进行了调试.以下贴上部分代码:
lines = cv2.HoughLinesP(mask_image,1,np.pi/180,50,minLineLength=20,maxLineGap=10)
for line in lines:
p1 = (line[0][0],line[0][1])
p2 = (line[0][2],line[0][3])
cv2.line(image,p1,p2,(0,0,255),1)
运行这部分代码的会发现拟合出很多条线,这是很久之前的程序,我也就懒得运行贴图了.原因的话是上面代码中Hough函数里50这个阈值导致的,如果我没记错的话,这个阈值越大,拟合出来的线越少,阈值越小,拟合出来的线越多.
那么为什么不直接把阈值调到很大来控制成只有左右两条轨道呢?这就是理想与现实的区别.如果控制成两条,怎么保证就是你想要的那两条线?之前也有提到过模型的不完美性.这就是原因所在.所以我们不但不能调大阈值,反而可能要稍稍调小一点阈值来确保我们想要的那两条轨迹被拟合出来了.
那接下来需要做的就是将这么多线进行一个筛选.
2020.7.23
那么以下就是初步筛选线数量的代码,主体思想就是我们将那些斜率相近,截距差在一定范围内的留下其中一条.
height=mask_image.shape[0]
weight=mask_image.shape[1]
xf1=0
xf2=0
yf1=0
yf2=0
# print(height):256
# print(weight):512
for i in range(height):
if mask_image[i,int(weight/2)]==0:
xf1=i
break
for i in range(height-1,-1,-1):
if mask_image[i,int(weight/2)]==0:
xf2=i
break
for j in range(weight):
if mask_image[int(height/2),j]==0:
yf1=j
break
for j in range(weight-1,-1,-1):
if mask_image[int(height/2),j]==0:
yf2=j
break
# print(xf1):0
# print(xf2):255
# print(yf1):0
# print(yf2):511
src=mask_image[xf1:xf2,yf1:yf2]
#cv2.imshow("src",src)
dstimage=cv2.cvtColor(src,cv2.COLOR_GRAY2RGB)
lines = cv2.HoughLinesP(mask_image,1,np.pi/180,50,minLineLength=20,maxLineGap=10)#50是阈值,过小过大会导致识别出直线过多或不足
k=[]
b=[]
xi=[]
#print(lines)
if lines is None:
sp=0
return xi_old
else:
for line in lines:
x1,y1,x2,y2 = line[0]
k1=float(y2-y1)/(x2-x1)
b1=float(y1)-k1*float(x1)
#cv2.line(dstimage,(int(-b1/k1),0),(int((xf2-xf1-1-b1)/k1),xf2-xf1-1),(200,200,100),2)
#cv2.line(dstimage,(int(xi_old*(yf2-yf1)/1280),0),(int(xi_old*(yf2-yf1)/1280),xf2-xf1-1),(200,200,100),2)
#cv2.line(dstimage,(int((xi_old-hashell)*(yf2-yf1)/1280),0),(int((xi_old-hashell)*(yf2-yf1)/1280),xf2-xf1-1),(200,100,200),2)
#cv2.line(dstimage,(int((xi_old+hashell)*(yf2-yf1)/1280),0),(int((xi_old+hashell)*(yf2-yf1)/1280),xf2-xf1-1),(200,100,200),2)
x1=float(x1*512)/(yf2-yf1)
x2=float(x2*512)/(yf2-yf1)
y1=float(y1*256)/(xf2-xf1)
y2=float(y2*256)/(xf2-xf1)
if x1==x2:
continue
k0=float(y2-y1)/(x2-x1)
b0=float(y1)-k0*float(x1)
if k0==0:
None
else:
xi0=float(256-b0)/k0#y=256时,x的值
k.append(k0)#在列表后面加k0
b.append(b0)
xi.append(xi0)
#print(k)
#print(b)
num=0
k_new=[]
b_new=[]
a_new=[]
xi_new=[]
for j in range(len(k)):#len()返回列表元素个数
t=0
for t in range(num):
if abs(k[j]-k_new[t])<=0.2 and abs(b[j]-b_new[t])<=100:
break
if t==num-1:
t=t+1
if t==num:
k_new.append(k[j])
b_new.append(b[j])
xi_new.append(xi[j])
a_new.append(1)
num=num+1
elif t<num:
k_new[t]=(k_new[t]*a_new[t]+k[j])/(a_new[t]+1)
b_new[t]=(b_new[t]*a_new[t]+b[j])/(a_new[t]+1)
xi_new[t]=(xi_new[t]*a_new[t]+xi[j])/(a_new[t]+1)
a_new[t]=a_new[t]+1
#print(k_new)
#print(b_new)
#print(a_new)
dst=cv2.cvtColor(src,cv2.COLOR_GRAY2RGB)
如下图左图是一开始静止时的理想情况,也是我们想要的,可当小车跑到管道拐弯处时,线会变得复杂起来,我截取了一张最糟糕的情况于下右图.所以我们还需要进行下一步的筛选.
2020.7.24
for j in range(num):
p1 = (int(-b_new[j]/k_new[j]),0)
p2 = (int(xi_new[j]),256)
cv2.line(image,p1,p2,(0,0,255),1)
#theta = np.arctan(k_new[j])/np.pi*180#反解斜率角
print('line',j,':','斜率k:',k_new[j],'截距b:',b_new[j],'xi_new[j]:',xi_new[j],'聚类直线数a:',a_new[j],'斜率角度theta:')
#print(pre_k)
这是昨天的画图代码,我感觉可以从聚类直线数出发,设定一个阈值来控制聚类直线数较少的线被画出来.结果告诉我没有这种理想的阈值存在,光是处理这段视频都不存在,更别说在管道里真正跑起来.
2020.7.26
所以我接下来考虑将每一帧的线与上一帧的线进行斜率差值计算,因为视频的一帧一帧间隔较小,所以直线斜率的差自然也不会很大.我需要将得出的结果进行排序,然后画出差值最小的那两个数对应索引的直线.以下是所写代码:
if i == 0:#画第一帧图像
pre_k=k_new#存储第一帧斜率
for j in [0,1]:
p1 = (int(-b_new[j]/k_new[j]),0)
p2 = (int(xi_new[j]),256)
cv2.line(image,p1,p2,(0,0,255),1)
#theta = np.arctan(k_new[j])/np.pi*180#反解斜率角
print('line',j,':','斜率k:',k_new[j],'截距b:',b_new[j],'xi_new[j]:',xi_new[j],'聚类直线数a:',a_new[j],'斜率角度theta:')
else:#第一帧之后
for j in range(num):#num可能大于2
if(a_new[j]>8):#限定条件:聚类直线数大于8时
e0=abs(abs(pre_k[0]-k_new[j])/pre_k[0])#这一帧的num根线与上一帧的两跟线差值比例
e1=abs(abs(pre_k[1]-k_new[j])/pre_k[1])
error.append(e0)
error.append(e1)
re1 = map(error.index, heapq.nsmallest(2, error)) #求最小的两个索引 nsmallest与nlargest相反,求最小
re2 = heapq.nsmallest(2, error) #求最小的两个元素
re1 = list(re1)#因为re1由map()生成的不是list,直接print不出来,添加list()就行了
print(error)
print(re1)
print(re2)
nre1 = list(map(lambda x:x//2,re1)) #每个元素整除以2,代表索引对应的num里第几根线
print(nre1)
#list(map(int,list(nre1)))
m=0
for j in list(nre1):
pre_k[m]=k_new[j]#存储上一帧斜率
m=m+1
p1 = (int(-b_new[j]/k_new[j]),0)
p2 = (int(xi_new[j]),256)
cv2.line(image,p1,p2,(0,0,255),1)
#theta = np.arctan(k_new[j])/np.pi*180#反解斜率角
print('line',j,':','斜率k:',k_new[j],'截距b:',b_new[j],'xi_new[j]:',xi_new[j],'聚类直线数a:',a_new[j],'斜率角度theta:')
#print(pre_k)
至次,视频前大半部分直线拟合解决了.
可中间一帧出现了一个bug:上面所标是上一帧的正常斜率,可在这一帧,由于图像识别模型的本身问题,当前的这一帧只存在的两根线而其中一根线是不符合的,但最终画了出来.(这里我突然有个想法,如果把之前的初步筛选只用在第一帧上面,之后直接都用做差排序筛选会有效果吗?下周试试)
所以导致了之后的一帧只画出了一根线,后面随之一直错了下去.