算法(二):汽车振动部件目标检测与跟踪(计算机视觉)

概览

  • 一、前言
  • 二、目标检测
    • 1.图像预处理
    • 2.帧间差分法
  • 三、目标跟踪
    • 1.原理
    • 2.应用
  • 四、总结

一、前言

在NVH性能开发过程中,需对整车进行振动试验,进行模态分析。在多个模态频率下,车门、天窗、轮胎或排气系统会产生共振, 形成最大的振幅。如图中排气系统在某个频率下产生了共振,在水平方向产生最大振幅。

二、目标检测

1.图像预处理

由于振动部件的颜色比较明显,振动幅度越大,红色越明显,而静止部分为蓝色。可以尝试将彩色图像RGB转换HSV空间(色差、饱和度、强度)后对蓝色背景进行过滤。

#过滤蓝色部分,将背景转换为白色
def data_prepare(image,index):
    imhsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    lower_blue = np.array([100,43,46])
    upper_blue = np.array([124,255,255])
    lower_white = np.array([0,0,255])
    upper_white = np.array([0,0,255])
    mask_blue = cv2.inRange(imhsv,lower_blue,upper_blue)
    mask_white = cv2.inRange(imhsv,lower_white,upper_white)
    mask=mask_blue|mask_white
    mask = 255 - mask
    res = cv2.bitwise_and(image,image,mask=mask)
    #将黑色背景转换为白色背景
    res[(res[:,:,0]+res[:,:,1]+res[:,:,2])==0]=[255,255,255]
    return res

2.帧间差分法

帧间差分法是将视频流中相邻两帧或相隔几帧图像的两幅图像像素值相减,并对相减后的图像进行阈值化来提取图像中的运动区域。因此,可采用帧间差分法检测振动部件。

1)帧间差分法的优点:算法简单,不易受环境光线影响。
2)帧间差分法的缺点:无法识别静止或运动速度很慢的目标;差分时容易产生孔洞,需做处理。

若相减两帧图像的帧数分别为第 k k k帧、第 k + 1 k+1 k+1帧,其帧图像分别为 f k ( x , y ) f_k(x,y) fk(x,y) f k + 1 ( x , y ) f_{k+1}(x,y) fk+1(x,y),差分图像二值化阈值为T,差分图像用D(x, y)表示,则帧间差分法的公式如下:

D ( x , y ) = { 1 ,          ∣ f k + 1 ( x , y ) − f k ( x , y ) ∣ > T 0 ,          o t h e r w i s e D(x,y) = \left\{\begin{array}{l}1,\;\;\;\;\left|f_{k+1}(x,y)-f_k(x,y)\right|>T\\0,\;\;\;\;otherwise\end{array}\right. D(x,y)={1,fk+1(x,y)fk(x,y)>T0,otherwise

#使用帧间差分法得到运动目标
def framediff(frame_lwpCV):
    global background
    gray_lwpCV = cv2.cvtColor(frame_lwpCV, cv2.COLOR_BGR2GRAY)
    gray_lwpCV = cv2.GaussianBlur(gray_lwpCV, (21, 21), 0)
    # 将第一帧设置为整个输入的背景
    if background is None:
        background = gray_lwpCV
        return
    # 对于每个从背景之后读取的帧都会计算其与背景之间的差异,并得到一个差分图(different map)。
    # 还需要应用阈值来得到一幅黑白图像,并通过下面代码来膨胀(dilate)图像,从而对孔(hole)和缺陷(imperfection)进行归一化处理
    diff = cv2.absdiff(background, gray_lwpCV)
    diff = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)[1] # 二值化阈值处理
    diff = cv2.dilate(diff, es, iterations=2) # 形态学膨胀
    return diff

三、目标跟踪

1.原理

光流法是一种利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的方法。基于光流法对振动部件进行跟踪并判断运动方向。

1)假设:摄像头采集到的两帧图像之间的像素灰度不变,即相邻帧之间的亮度恒定;相邻的两帧像素具有相对运动;保持空间一致性;即,同一子图像的像素点具有相同的运动。
2)角点检测:基于灰度图像的Shi-Tomasi角点检测算法(Harris角点检测算法的改进),计算灰度发生较大变化时所对应的位置,找到纹理丰富的物体边缘点。
3)角点跟踪:根据角点的灰度及周围同一子图像的像素点的灰度值进行角点的跟踪。

考虑一个像素I(x,y,t)在第一帧的光强度(其中t代表其所在的时间维度)。它移动了 (dx,dy)的距离到下一帧,用了dt时间。因为是同一个像素点,我们认为该像素在运动前后的光强度是不变的,即 I ( x , y , t ) = I ( x + d x , y + d y , t + d t ) I(x,y,t)=I(x+dx, y+dy, t+dt) I(x,y,t)=I(x+dx,y+dy,t+dt),经过泰勒展开得到
I ( x , y , t ) = I ( x , y , t ) + ∂ I ∂ x d x + ∂ I ∂ y d y + ∂ I ∂ t d t + ε I(x,y,t)=I(x,y,t)+\frac{\partial I}{\partial x}dx+\frac{\partial I}{\partial y}dy+\frac{\partial I}{\partial t}dt+\varepsilon I(x,y,t)=I(x,y,t)+xIdx+yIdy+tIdt+ε
其中,ε代表二阶无穷小项,可忽略不计,由此可得
∂ I ∂ x d x d t + ∂ I ∂ y d y d t + ∂ I ∂ t = 0 \frac{\partial I}{\partial x}\frac{dx}{dt}+\frac{\partial I}{\partial y}\frac{dy}{dt}+\frac{\partial I}{\partial t}=0 xIdtdx+yIdtdy+tI=0
I x = ∂ I ∂ x , I y = ∂ I ∂ y , I t = ∂ I ∂ t I_x=\frac{\partial I}{\partial x},I_y=\frac{\partial I}{\partial y},I_t=\frac{\partial I}{\partial t} Ix=xI,Iy=yI,It=tI,由于u,v分别为光流分别为沿X轴与Y轴的速度矢量,因此
I x d x d t + I y d y d t + I t = 0 I_x\frac{dx}{dt}+I_y\frac{dy}{dt}+I_t=0 Ixdtdx+Iydtdy+It=0
其中Ix,Iy,It均可由图像数据求得,而(u,v)即为所求光流矢量。约束方程只有一个,而方程的未知量有两个,这种情况下无法求得u和v的确切值。此时需要引入另外的约束条件,从不同的角度引入约束条件,计算出光流场。

2.应用


#使用光流法寻找特征点的轨迹
def tracking(image,listnum):
    global tracks
    global tracksflag
    global prev_gray
    track_len = 10
    frame_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#转化为灰度图
    vis = image.copy()#赋值frame的值,不覆盖frame本身

    if tracksflag[listnum] > 0:#检测到角点后进行光流跟踪 
        img0, img1 = prev_gray, frame_gray
        p0 = np.float32([tr[-1] for tr in tracks]).reshape(-1, 1, 2)#对np数组进行重塑
        #前一帧的角点和当前帧的图像作为输入来得到角点在当前帧的位置
        p1, st, err = cv2.calcOpticalFlowPyrLK(img0, img1, p0, None, **lk_params)
        #当前帧跟踪到的角点及图像和前一帧的图像作为输入来找到前一帧的角点位置
        p0r, st, err = cv2.calcOpticalFlowPyrLK(img1, img0, p1, None, **lk_params) 
        d = abs(p0-p0r).reshape(-1, 2).max(-1)#得到角点回溯与前一帧实际角点的位置变化关系
        good = d < 1#判断d内的值是否小于1,大于1跟踪被认为是错误的跟踪点
        new_tracks = []
        #将跟踪正确的点列入成功跟踪点
        for tr, (x, y), good_flag in zip(tracks, p1.reshape(-1, 2), good):
            if not good_flag:
                continue
            tr.append((x, y))
            if len(tr) > track_len:
                del tr[0]
            new_tracks.append(tr)
#            cv2.circle(vis, (x, y), 2, (0, 255, 0), -1)#画圆
        tracks = new_tracks
    
        #以上一帧角点为初始点,当前帧跟踪到的点为终点划线
        cv2.polylines(vis, [np.int32(tr) for tr in tracks], False, (0, 0, 0))
#        print('寻找轨迹',tracks)
    else:
        mask = np.zeros_like(frame_gray)#初始化和视频大小相同的图像
        mask[:] = 255#将mask赋值255也就是算全部图像的角点
#        for x, y in [np.int32(tr[-1]) for tr in tracks]:#跟踪的角点画圆
#            cv2.circle(mask, (x, y), 5, 0, -1)
        p = cv2.goodFeaturesToTrack(frame_gray, mask = mask, **feature_params)#角点检测
        if p is not None:
            for x, y in np.float32(p).reshape(-1, 2):
                tracks.append([(x, y)])#将检测到的角点放在待跟踪序列中        
                tracksflag[listnum]=1
    prev_gray = frame_gray

#根据轨迹上的点可以得到运动向量,将该向量转换运动方向
def direction_judge(tracklines):
    classification='0'
    trvec=[]
    ang=[]
    for listnum in range(0,len(tracklines)):
        dismax=0
        indexmax=0
        trvector=[0,0]
        for index in range(0,len(tracklines[listnum])):
            #将同一个视图中同一个轨迹上最后一个轨迹点和第一个轨迹点做差得到运动向量,将同一个视图中所有特征点的运动向量累加得到整体的运动向量
            trackline_x=[x[0] for x in tracklines[listnum][index]]
            trackline_y=[y[1] for y in tracklines[listnum][index]]
            trackline=[trackline_x[-1]-trackline_x[0],trackline_y[-1]-trackline_y[0]]
            trvector=[a+b for a, b in zip(trvector,trackline)]
#        length=len(trackline_x)
#            trvector=[sum(trackline_x)/length,sum(trackline_y)/length]
#        trvector=trackline[-1][0]-trackline[0][0],trackline[-1][1]-trackline[0][1]
        trvector=np.array(trvector)        
        unitvector=np.array([1,0])
        #计算运动向量与x轴正方向的角度
        interangel=np.arccos(trvector.dot(unitvector)/np.sqrt(trvector.dot(trvector)))*180/np.pi
        if trvector[1] < 0:
            angel=interangel+180
        else:
            angel=interangel
        trvec.append(trvector)
        ang.append(angel)
    #特殊点挑选->lateral
    if ang[0] < 20:
        classification='2'
    #主视图和俯视图运动部件向右/左水平移运动->lateral
    elif (((ang[2] < 40) or ((ang[2] > 150) and (ang[2] < 220)) or (ang[2] > 350)) and ((ang[3] < 10) or ((ang[3] > 150) and (ang[3] < 220)) or (ang[3] > 350))):
        classification='2'
    #主视图和俯视图运动部件运动方向相反或者右视图运动部件向上/下运动->bounce
    elif (abs(ang[2]-ang[3]) > 110 ) and (abs(ang[2]-ang[3]) < 190 ) or (((ang[1] > 70) and (ang[1] < 110)) or ((ang[1] > 230) and (ang[1] < 310))):
        classification='1'
    else:
        classification='3'
        
    for case in switch(classification):
        if case('1'):
            print('It is bounce')
            break
        if case('2'):
            print('It is lateral')
            break
        if case('3'):
            print('It is front-after')
            break
        if case():
            print('There is no classification')
    print(ang)

四、总结

本文基于整车振动时振动部件的四个视角,通过帧间差分法对振动部件进行检测,并且采用光流法对振动部件进行跟踪,对振动方向予以准确的判断。

参考文献
[1]背景提取算法
[2]光流法简单介绍
[3]计算机视觉–光流法(optical flow)简介
[4]总结:光流–LK光流–基于金字塔分层的LK光流–中值流

你可能感兴趣的:(算法)