使用 OpenCV (Python) 改进轮廓检测
Posted
技术标签:
【中文标题】使用 OpenCV (Python) 改进轮廓检测【英文标题】:Improve contour detection with OpenCV (Python) 【发布时间】:2016-03-27 03:44:26 【问题描述】:我正在尝试从照片中识别卡片。我设法在理想照片上做我想做的事,但我现在很难应用相同的程序,但光照略有不同,等等。所以问题是如何使以下轮廓检测更加稳健。
我需要分享我的大部分代码,以便获取者能够制作感兴趣的图像,但我的问题仅与最后一个块和图像有关。
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)
然后检测到卡片:
# 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)
视角修正:
#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.
那是我遇到了问题。我想检测形状的轮廓。我发现最好的方法是在灰度图像上使用bilateralFilter
和AdaptativeThreshold
的组合:
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)
这与我想要的非常接近,但是我怎样才能改进它以获得白色的封闭轮廓,而其他一切都是黑色的?
【问题讨论】:
您是否尝试过使用 honvex 船体来闭合轮廓?要消除白噪声,请尝试扩张和腐蚀。 【参考方案1】:为什么不直接使用 Canny 并在找到轮廓后应用透视校正(因为它似乎模糊了边缘)?例如,使用您在问题中提供的小图片(使用较大的图片可能会更好):
基于您代码的某些部分:
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 边缘检测器:
然后你可以扩张/侵蚀,校正透视,去除最大轮廓等。
【讨论】:
【参考方案2】:除了右下角的图片外,一般应该按照以下步骤进行:
-
扩张和侵蚀二进制掩码,以弥合轮廓片段之间的任何一两个像素间隙。
使用最大抑制将沿形状边界的厚二元蒙版变成薄边。
正如前面在管道中使用的那样,使用 cvFindcontours 来识别闭合轮廓。可以测试该方法识别的每个轮廓是否闭合。
作为此类问题的一般解决方案,我建议您尝试我的算法来查找给定点周围的闭合轮廓。检查active segmentation with fixation
【讨论】:
目前,我使用三脚架克服了这个问题。谢谢你的建议,以后有需要我会试试的。我不精通图像处理,但您的算法令人印象深刻!但就我而言,我这样做只是为了好玩,所以我不想去实现新算法。顺便问一下,您认为什么时候从标准 PC 上的初始图像中找出卡片的形状、纹理和颜色比较合理?我的程序目前大约需要 5 秒,我想知道我是否可以轻松做得更好。 对于 640x480 大小的图像,您在原始帖子中描述的步骤不应超过 1 秒。 有许多选项可以描述闭合轮廓的形状,例如使用椭圆傅立叶描述符、形状上下文(匹配期间的大量计算成本)、基于矩的描述符等等。同样,您也有许多选项来描述给定区域的纹理。但是,如果您打算通过以后可以用来匹配的特征向量来表示未变形的卡片,请使用基于方向的描述符(如 SIFT/SURF)来描述您的卡片。它们相当可靠。以上是关于使用 OpenCV (Python) 改进轮廓检测的主要内容,如果未能解决你的问题,请参考以下文章
OpenCV-Python实战(11)——OpenCV轮廓检测相关应用