在圆内找到轮廓

时间:2018-10-12 10:28:45

标签: python opencv contour

我正在尝试编写一个程序,该程序可以检测在圆透镜上切出的直线,如图像左侧所示:

enter image description here

现在,我尝试使用Canny边缘检测,Hough Line Transform和findContour来单独分隔线,但是我这样做没有成功。

我还尝试通过首先检测镜头的外圆并在ROI(检测到的圆)内执行轮廓搜索来检测线条,但是我在镜头上得到了随机的线条,但没有得到想要的输出。

1 个答案:

答案 0 :(得分:2)

因此,我首先要指出,您的图像非常嘈杂。这意味着仅由于噪声而仅寻找轮廓,边缘或线条可能无法正常工作。这使任务非常困难。如果您正在寻找一种使这种任务自动化的方法,我建议您花些力气找到合适的照明(我认为经典的圆顶灯就足够了),因为这样可以减少图像上的噪点(减少反射)。 ),因此制作这样的算法会更容易。

话虽如此。我已经举例说明了我将如何尝试完成这样的任务。请注意,该解决方案可能不适用于其他图像,但是在此示例中,结果是相当不错的。也许它将为您提供有关如何解决此问题的新观点。

首先,在将图像转换为具有OTSU阈值的二进制图像之前,我将尝试执行直方图均衡化。之后,我将在图像上执行打开(侵蚀然后扩张):

enter image description here

之后,我将在最大轮廓上制作一个边界框。使用x,y,h,w可以计算边界框的中心,该边界框将用作我要创建的ROI的中心。在图像的副本上绘制半径稍小于w / 2的圆,并在半径等于w / 2的新蒙版上绘制一个圆。然后执行按位操作:

enter image description here

现在您有了ROI,并且必须再次对其进行阈值处理,以使边界没有噪声并搜索轮廓:

enter image description here

现在您可以看到有两个轮廓(内部和外部)。因此,现在您可以提取镜头切割区域。您可以通过计算内部轮廓和外部轮廓的每个点之间的距离来实现。两点之间的距离的公式是sqrt((x2-x1)^2 + (y2-y2)^2)。设置此距离的阈值,以便如果该距离小于某个整数,并在图像上的这两个点之间绘制一条线。我用蓝线画了距离。之后,将图像转换为HSV色彩空间,并再次按位操作对其进行遮罩,因此剩下的只是那些蓝线:

enter image description here

再次执行OTSU阈值,然后选择最大轮廓(那些蓝线),并在轮廓中拟合一条线。在原始图像上画线,您将得到最终结果:

enter image description here

示例代码:

import cv2
import numpy as np

### Perform histogram equalization and threshold with OTSU.
img = cv2.imread('lens.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
equ = cv2.equalizeHist(gray)
_, thresh = cv2.threshold(equ,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

### Perform opening (erosion followed by dilation) and search for contours.
kernel = np.ones((2,2),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
_, contours, hierarchy = cv2.findContours(opening,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

### Select the biggest one and create a bounding box.
### This will be used to calculate the center of your ROI.
cnt = max(contours, key=cv2.contourArea)

### Calculate x and y of the center.
x,y,w2,h2 = cv2.boundingRect(cnt)
center_x = int(x+(w2/2))
center_y = int(y+(h2/2))

### Create the radius of your inner circle ROI and draw it on a copy of the image.
img2 = img.copy()
radius = int((w2/2)-20)
cv2.circle(img2,(center_x,center_y), radius, (0,0,0), -1)

### Create the radius of your inner circle ROI and draw it on a blank mask.
radius_2 = int(w2/2)
h,w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)
cv2.circle(mask,(center_x,center_y), radius_2, (255,255,255), -1)

### Perform a bitwise operation so that you will get your ROI
res = cv2.bitwise_and(img2, img2, mask=mask)

### Modify the image a bit to eliminate noise with thresholding and closing.
_, thresh = cv2.threshold(res,190,255,cv2.THRESH_BINARY)
kernel = np.ones((3,3),np.uint8)
closing = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,kernel, iterations = 2)

### Search for contours again and select two biggest one.
gray = cv2.cvtColor(closing,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
area = sorted(contours, key=cv2.contourArea, reverse=True)
contour1 = area[0]
contour2 = area[1]

### Iterate through both contours and calculate the minimum distance.
### If it is less than the threshold you provide, draw the lines on the image.
### Forumula is sqrt((x2-x1)^2 + (y2-y2)^2).
for i in contour1:
    x = i[0][0]
    y = i[0][1]
    for j in contour2:
        x2 = j[0][0]
        y2 = j[0][1]
        dist = np.sqrt((x2-x)**2 + (y2-y)**2)
        if dist < 12:
            xy = (x,y)
            x2y2 = (x2,y2)
            line = (xy,x2y2)
            cv2.line(img2,xy,x2y2,(255,0,0),2)
        else:
            pass

### Transform the image to HSV colorspace and mask the result.
hsv = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)
lower_blue = np.array([110,50,50])
upper_blue = np.array([130,255,255])
mask = cv2.inRange(hsv, lower_blue, upper_blue)
res = cv2.bitwise_and(img2,img2, mask= mask)

### Search fot contours again.
gray = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)

### Fit a line through the contour and draw it on the original image.
[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
left = int((-x*vy/vx) + y)
right = int(((w-x)*vy/vx)+y)
cv2.line(img,(w-1,right),(0,left),(0,0,255),2)

### Display the result.
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()