使用OpenCV(Python)改进轮廓检测

16
我正在尝试从照片中识别卡片。我已经在理想的照片上实现了我想要的效果,但是现在在应用相同过程时遇到了一些困难,例如光线略有不同等。因此,问题是如何使以下轮廓检测更加稳健。
我需要分享我的代码的大部分内容,以便接受者能够制作感兴趣的图像,但是我的问题仅涉及最后一个块和图像。
import numpy as np
import cv2
from matplotlib import pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
import math

img = cv2.imread('image.png')
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
plt.imshow(img)

enter image description here

然后检测卡片:
# Prepocess
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(1,1),1000)
flag, thresh = cv2.threshold(blur, 120, 255, cv2.THRESH_BINARY)
# Find contours
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea,reverse=True) 
# Select long perimeters only
perimeters = [cv2.arcLength(contours[i],True) for i in range(len(contours))]
listindex=[i for i in range(15) if perimeters[i]>perimeters[0]/2]
numcards=len(listindex)
# Show image
imgcont = img.copy()
[cv2.drawContours(imgcont, [contours[i]], 0, (0,255,0), 5) for i in listindex]
plt.imshow(imgcont)

enter image description here

透视已校正:
#plt.rcParams['figure.figsize'] = (3.0, 3.0)
warp = range(numcards)
for i in range(numcards):
    card = contours[i]
    peri = cv2.arcLength(card,True)
    approx = cv2.approxPolyDP(card,0.02*peri,True)
    rect = cv2.minAreaRect(contours[i])
    r = cv2.cv.BoxPoints(rect)

    h = np.array([ [0,0],[399,0],[399,399],[0,399] ],np.float32)
    approx = np.array([item for sublist in approx for item in sublist],np.float32)
    transform = cv2.getPerspectiveTransform(approx,h)
    warp[i] = cv2.warpPerspective(img,transform,(400,400))

# Show perspective correction
fig = plt.figure(1, (10,10))
grid = ImageGrid(fig, 111, # similar to subplot(111)
                nrows_ncols = (4, 4), # creates 2x2 grid of axes
                axes_pad=0.1, # pad between axes in inch.
                aspect=True, # do not force aspect='equal'
                )

for i in range(numcards):
    grid[i].imshow(warp[i]) # The AxesGrid object work as a list of axes.

enter image description here

这就是我遇到的问题。我想要检测形状的轮廓。我发现最好的方法是在灰度图像上使用 bilateralFilterAdaptativeThreshold 的组合:

fig = plt.figure(1, (10,10))
grid = ImageGrid(fig, 111, # similar to subplot(111)
                nrows_ncols = (4, 4), # creates 2x2 grid of axes
                axes_pad=0.1, # pad between axes in inch.
                aspect=True, # do not force aspect='equal'
                )
for i in range(numcards):
    image2 = cv2.bilateralFilter(warp[i].copy(),10,100,100)
    grey = cv2.cvtColor(image2,cv2.COLOR_BGR2GRAY)
    grey2 = cv2.cv.AdaptiveThreshold(cv2.cv.fromarray(grey), cv2.cv.fromarray(grey), 255, cv2.cv.CV_ADAPTIVE_THRESH_MEAN_C, cv2.cv.CV_THRESH_BINARY, blockSize=31, param1=6)
    grid[i].imshow(grey,cmap=plt.cm.binary) 

enter image description here

这非常接近我的要求,但是如何改进它以获得白色封闭轮廓和其他所有内容为黑色?

你尝试过使用凸包算法来闭合轮廓吗?为了去除白噪声,可以尝试膨胀和腐蚀操作。 - MrOnyszko
2个回答

3
为什么不直接使用Canny算法,并在找到轮廓后应用透视校正(因为它似乎会模糊边缘)?例如,使用您在问题中提供的小图像(在更大的图像上结果可能更好):

enter image description here

基于您代码的某些部分:

import numpy as np
import cv2

import math

img = cv2.imread('image.bmp')

# Prepocess
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
flag, thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)

# Find contours
img2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True) 

# Select long perimeters only
perimeters = [cv2.arcLength(contours[i],True) for i in range(len(contours))]
listindex=[i for i in range(15) if perimeters[i]>perimeters[0]/2]
numcards=len(listindex)

card_number = -1 #just so happened that this is the worst case
stencil = np.zeros(img.shape).astype(img.dtype)
cv2.drawContours(stencil, [contours[listindex[card_number]]], 0, (255, 255, 255), cv2.FILLED)
res = cv2.bitwise_and(img, stencil)
cv2.imwrite("out.bmp", res)
canny = cv2.Canny(res, 100, 200)
cv2.imwrite("canny.bmp", canny)

首先,为了简单起见,除了一张卡片以外的所有内容都要删除,然后应用Canny边缘检测器:

enter image description hereenter image description here

然后您可以进行膨胀/腐蚀、矫正透视、移除最大轮廓等操作。

2

除了右下角的图像以外,以下步骤通常适用:

  1. 对二值掩模进行膨胀和腐蚀,以填补轮廓片段之间的一个或两个像素间隙。
  2. 使用最大抑制方法将形状边界上的厚二值掩模转换为细边缘。
  3. 如同在管道中先前所用,使用cvFindcontours来识别封闭轮廓。该方法识别到的每个轮廓都可以测试其是否闭合。
  4. 作为此类问题的一般解决方案,我建议您尝试我的算法来查找给定点周围的封闭轮廓。请参考active segmentation with fixation

暂时,我通过使用三脚架解决了这个问题。谢谢你的建议,如果需要的话我会试试它们。我不太懂图像处理,但是你的算法相当令人印象深刻!但在我的情况下,我只是出于乐趣而这样做,所以我不想去实现新算法。 顺便问一下,按照你的看法,从初始图像中找到卡片的形状、纹理和颜色的合理时间是多长?我的程序目前需要大约5秒钟,我想知道我是否可以轻松地做得更好。 - anderstood
你在原帖中描述的步骤对于一个640x480大小的图像来说不应该超过1秒钟。 - Ajay
1
有许多选项可以描述封闭轮廓的形状,例如使用椭圆傅里叶描述符、形状上下文(匹配过程中计算成本较高)、基于矩的描述符等等。同样地,您也有许多选项来描述给定区域的纹理。然而,如果您的意图是通过特征向量来表示未扭曲的卡片,并在以后进行匹配,请使用基于方向的描述符(如SIFT/SURF)来描述您的卡片。它们相当可靠。 - Ajay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接