Nikie的答案解决了我的问题,但他的答案是在Mathematica中。所以我认为我应该在这里给出其OpenCV改编版。但是在实施之后,我可以看到OpenCV代码比nikie的mathematica代码大得多。而且,我在OpenCV中找不到nikie完成的插值方法(尽管可以使用scipy完成,但是我会在时间到时告诉它。)
1.图像预处理(关闭操作)
import cv2
import numpy as np
img = cv2.imread('dave.jpg')
img = cv2.GaussianBlur(img,(5,5),0)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
mask = np.zeros((gray.shape),np.uint8)
kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))
close = cv2.morphologyEx(gray,cv2.MORPH_CLOSE,kernel1)
div = np.float32(gray)/(close)
res = np.uint8(cv2.normalize(div,div,0,255,cv2.NORM_MINMAX))
res2 = cv2.cvtColor(res,cv2.COLOR_GRAY2BGR)
结果:
2.找到数独广场并创建蒙版图像
thresh = cv2.adaptiveThreshold(res,255,0,1,19,2)
contour,hier = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
max_area = 0
best_cnt = None
for cnt in contour:
area = cv2.contourArea(cnt)
if area > 1000:
if area > max_area:
max_area = area
best_cnt = cnt
cv2.drawContours(mask,[best_cnt],0,255,-1)
cv2.drawContours(mask,[best_cnt],0,0,2)
res = cv2.bitwise_and(res,mask)
结果:
3.查找垂直线
kernelx = cv2.getStructuringElement(cv2.MORPH_RECT,(2,10))
dx = cv2.Sobel(res,cv2.CV_16S,1,0)
dx = cv2.convertScaleAbs(dx)
cv2.normalize(dx,dx,0,255,cv2.NORM_MINMAX)
ret,close = cv2.threshold(dx,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernelx,iterations = 1)
contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
x,y,w,h = cv2.boundingRect(cnt)
if h/w > 5:
cv2.drawContours(close,[cnt],0,255,-1)
else:
cv2.drawContours(close,[cnt],0,0,-1)
close = cv2.morphologyEx(close,cv2.MORPH_CLOSE,None,iterations = 2)
closex = close.copy()
结果:
4.查找水平线
kernely = cv2.getStructuringElement(cv2.MORPH_RECT,(10,2))
dy = cv2.Sobel(res,cv2.CV_16S,0,2)
dy = cv2.convertScaleAbs(dy)
cv2.normalize(dy,dy,0,255,cv2.NORM_MINMAX)
ret,close = cv2.threshold(dy,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernely)
contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
x,y,w,h = cv2.boundingRect(cnt)
if w/h > 5:
cv2.drawContours(close,[cnt],0,255,-1)
else:
cv2.drawContours(close,[cnt],0,0,-1)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,None,iterations = 2)
closey = close.copy()
结果:
当然,这不是很好。
5.查找网格点
res = cv2.bitwise_and(closex,closey)
结果:
6.纠正缺陷
在这里,nikie进行某种插值,对此我并不了解。而且我找不到此OpenCV的任何相应功能。 (也许在那里,我不知道)。
查看此SOF,它说明了如何使用SciPy进行此操作,我不想使用它: OpenCV中的图像转换
因此,在这里,我将每个子正方形的四个角用作每个变角透视图。
为此,首先我们找到质心。
contour, hier = cv2.findContours(res,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
centroids = []
for cnt in contour:
mom = cv2.moments(cnt)
(x,y) = int(mom['m10']/mom['m00']), int(mom['m01']/mom['m00'])
cv2.circle(img,(x,y),4,(0,255,0),-1)
centroids.append((x,y))
但是结果质心将不会排序。查看下图以查看其顺序:
因此,我们从左到右,从上到下对它们进行排序。
centroids = np.array(centroids,dtype = np.float32)
c = centroids.reshape((100,2))
c2 = c[np.argsort(c[:,1])]
b = np.vstack([c2[i*10:(i+1)*10][np.argsort(c2[i*10:(i+1)*10,0])] for i in xrange(10)])
bm = b.reshape((10,10,2))
现在看下面他们的命令:
最后,我们应用转换并创建尺寸为450x450的新图像。
output = np.zeros((450,450,3),np.uint8)
for i,j in enumerate(b):
ri = i/10
ci = i%10
if ci != 9 and ri!=9:
src = bm[ri:ri+2, ci:ci+2 , :].reshape((4,2))
dst = np.array( [ [ci*50,ri*50],[(ci+1)*50-1,ri*50],[ci*50,(ri+1)*50-1],[(ci+1)*50-1,(ri+1)*50-1] ], np.float32)
retval = cv2.getPerspectiveTransform(src,dst)
warp = cv2.warpPerspective(res2,retval,(450,450))
output[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1] = warp[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1].copy()
结果:
结果几乎与nikie相同,但是代码长度很大。也许可以使用更好的方法,但是在那之前,这种方法行之有效。
关于方舟。
0
我当时在做一个有趣的项目:使用OpenCV(如Google护目镜等)从输入图像中解决数独。我已经完成了任务,但是最后我遇到了一个小问题。
我使用OpenCV 2.3.1的Python API进行了编程。
以下是我所做的:
找到拐角点。
例如下面给出:
( 请注意,绿线正确地与数独的真实边界重合,因此数独可以正确变形 。请检查下一张图片)
使图像变形为完美的正方形
例如图片:
执行OCR(为此我使用了我在OpenCV-Python的简单数字识别OCR中给出的方法)
而且该方法效果很好。
问题:
看看这张图片。
在此图像上执行步骤4会得到以下结果:
画出的红线是原始轮廓,是数独边界的真实轮廓。
画出的绿线是近似轮廓,它将是变形图像的轮廓。
数独顶部的绿线和红线之间当然有区别。因此,在扭曲时,我并没有获得数独的原始边界。
我的问题 :
如何在数独的正确边界(即红线)上扭曲图像,或者如何消除红线和绿线之间的差异? OpenCV中有什么方法吗?