使用 OpenCV 测量弯曲线的真实角度

Measuring true angle of a bended wire with OpenCV

我有一个弯曲电线的机器人设备,我尝试通过查看所需的真实角度测量值来测量弯曲是否成功。

我的想法是检测电线并为电线的每一段画一条线,然后它就变成了一个小三角问题。但是我找不到绘制和获取这些线坐标的方法。

我对图像处理还很陌生,所以请原谅我粗俗的术语。

这是一个非常宽泛的问题,所以我的回答也会有些宽泛。一种解决方法是:

  1. 获取线变白、背景黑的二值图像的阈值
  2. 执行连通分量标记
  3. 在黑色新图像上只绘制第二大组件(忽略左边的圆盘形状)
  4. 垂直求和此图像以找到从导线底部到导线的过渡(在列总和值中跳转)
  5. 确定从导线顶端(最左边的非零和列)到导线底部之前的每一列最上面的白色像素的 y 坐标
  6. 计算每个最上面的像素到最左边的顶部像素和最右边的顶部像素之间的线(就在基线之前)的距离
  7. 将拐点定位为该距离最大的列中的顶部像素
  8. 计算最左边的顶部像素和拐点之间的线的角度
  9. 如果该部分也可以倾斜,则与水平线或拐点与底部之前最右上角像素之间的线的角度进行比较

这看起来是个有趣的问题。我们可以利用弯曲线材和未弯曲线材的垂直边缘强度不同这一事实。当我们用垂直 Sobel 过滤器过滤时,未弯曲的电线将 return 一个更高的值。

y方向的sobel滤波结果

通过提取不同强度的区域(斑点)来提取弯曲和未弯曲的线。

Stronger edge will correspond to the unbent wire.

Weaker edge will correspond to the bent wire.

利用opencv的斑点分析函数minRectArea,我们可以得到斑点的角度、大小和中心位置。

角度值w.r.t。横线

Unbent Wire : 3.94 degree

Bent Wire : 21.37 degree

获取上述结果的代码如下:

import cv2
import numpy as np 
import matplotlib.pyplot as plt

# extract blob with largest area
def get_best_blob(blobs):
    best_blob = None
    best_size = 0
    for i,blob in enumerate(blobs):
        rot_rect = cv2.minAreaRect(blob)
        (cx,cy),(sx,sy),angle = rot_rect
        if sx * sy >best_size :
            best_blob = rot_rect
            best_size = sx * sy
    
    return best_blob

def draw_blob_rect(frame,blob,color):
    box = cv2.boxPoints(blob)
    box = np.int0(box)    
    frame = cv2.drawContours(frame,[box],0,color,1)

    return frame

def process():
    image = cv2.imread("wire.png")

    # Change to grayscale
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    
    # Reduce size for speed
    gray = cv2.resize(gray,(0,0),fx=0.5,fy=0.5)

    # Extract wire region
    _ , thresh = cv2.threshold(gray,100,255,cv2.THRESH_BINARY_INV)

    # morphology kernel
    kernel = np.array((
            [1, 1, 1],
            [1, 1, 1],
            [1, 1, 1]
            ), dtype="int")
    
    # Get y-edges
    dy = cv2.Sobel(thresh,cv2.CV_32F,0,1,ksize=21)

    # remove negative values
    dy = dy * dy
    
    # Normalize it to 255
    cv2.normalize(dy,dy,norm_type=cv2.NORM_MINMAX,alpha=255)
    dy = dy.astype('uint8')

    # Extract relevant information
    # Stronger edge is the original part
    # Weaker edge is the bended part
    _ , strong = cv2.threshold(dy,0.95 * 255,255,cv2.THRESH_BINARY)
    _ , mid = cv2.threshold(dy,0.5 * 255,255,cv2.THRESH_BINARY)


    # Morphological closing to remove holes 
    strong_temp = cv2.dilate(strong,kernel, iterations=5)
    strong = cv2.erode(strong_temp,kernel, iterations=5)
    
    # remove the strong part from the mid
    mid = cv2.subtract(mid,strong_temp)

    # Morphological closing to remove holes 
    mid_temp = cv2.dilate(mid,kernel, iterations=5)
    mid = cv2.erode(mid_temp,kernel, iterations=5)

    # find the blobs for each bin image
    _,strong_blobs,_ = cv2.findContours(strong,cv2.RETR_EXTERNAL ,cv2.CHAIN_APPROX_SIMPLE)
    _,mid_blobs,_ = cv2.findContours(mid,cv2.RETR_EXTERNAL ,cv2.CHAIN_APPROX_SIMPLE)

    # get the blob with the largest area
    best_strong = get_best_blob(strong_blobs)
    best_mid = get_best_blob(mid_blobs)

    # print the angle
    print( "strong_angle",90 + best_strong[2])
    print( "mid_angle",90 + best_mid[2])

    
    # Draw the segmented Box region
    display_frame = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
    display_frame = draw_blob_rect(display_frame,best_strong,(0,0,255))
    display_frame = draw_blob_rect(display_frame,best_mid,(0,255,255))
    
    # draw result    
    cv2.imshow("display_frame",display_frame)
    cv2.imshow("mid",mid)
    cv2.imshow("strong",strong)
    cv2.imshow("image",image)
    cv2.imshow("dy",dy)
    cv2.waitKey(0)



if __name__=='__main__':
    process()