请看这里,它似乎是确切的答案。
这里有我对上述代码进行了微调以进行文本提取(还带有掩码)的版本。
下面是先前文章中的原始代码,“移植”到Python 3、OpenCV 3中,并添加了MSER和边界框。我的版本与下面的版本主要区别在于如何定义分组距离:我的版本是以文本为导向的,而下面的版本是自由几何距离。
import sys
import cv2
import numpy as np
def find_if_close(cnt1,cnt2):
row1,row2 = cnt1.shape[0],cnt2.shape[0]
for i in range(row1):
for j in range(row2):
dist = np.linalg.norm(cnt1[i]-cnt2[j])
if abs(dist) < 25:
return True
elif i==row1-1 and j==row2-1:
return False
img = cv2.imread(sys.argv[1])
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv2.imshow('input', img)
ret,thresh = cv2.threshold(gray,127,255,0)
mser=False
if mser:
mser = cv2.MSER_create()
regions = mser.detectRegions(thresh)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions[0]]
contours = hulls
else:
thresh = cv2.bitwise_not(thresh)
im2,contours,hier = cv2.findContours(thresh,cv2.RETR_EXTERNAL,2)
cv2.drawContours(img, contours, -1, (0,0,255), 1)
cv2.imshow('base contours', img)
LENGTH = len(contours)
status = np.zeros((LENGTH,1))
print("Elements:", len(contours))
for i,cnt1 in enumerate(contours):
x = i
if i != LENGTH-1:
for j,cnt2 in enumerate(contours[i+1:]):
x = x+1
dist = find_if_close(cnt1,cnt2)
if dist == True:
val = min(status[i],status[x])
status[x] = status[i] = val
else:
if status[x]==status[i]:
status[x] = i+1
unified = []
maximum = int(status.max())+1
for i in range(maximum):
pos = np.where(status==i)[0]
if pos.size != 0:
cont = np.vstack(contours[i] for i in pos)
hull = cv2.convexHull(cont)
unified.append(hull)
cv2.drawContours(img,contours,-1,(0,0,255),1)
cv2.drawContours(img,unified,-1,(0,255,0),2)
for c in unified:
(x,y,w,h) = cv2.boundingRect(c)
cv2.rectangle(img, (x,y), (x+w,y+h), (255, 0, 0), 2)
cv2.imshow('result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
示例输出(黄色斑块在二进制阈值转换下被忽略)。红色:原始轮廓,绿色:统一轮廓,蓝色:边界框。
也许没有必要使用MSER,简单的findContours可能会很好地工作。
------------------------
从这里开始是我在找到上面的代码之前的旧答案。我仍然保留它,因为它描述了几种不同的方法,可能更容易/更适合某些情况。
一个快速而简单的技巧是在MSER(或某些稀疏/腐蚀)之前添加小的高斯模糊和高阈值。实际上,您只需使文本更加粗体,以便填补小间隙。显然,您可以稍后放弃此版本并从原始版本裁剪。
否则,如果您的文本是按行排列的,则可以尝试检测平均行中心(例如制作Y坐标的直方图并查找峰值)。然后,对于每一行,查找具有接近平均X的片段。如果文本嘈杂/复杂,则相当脆弱。
如果您不需要拆分每个字母,则获取整个单词的边界框可能更容易:只需基于片段之间的最大水平距离(使用轮廓的左/右最点)将其分组。然后在每个组内使用最左和最右的框来找到整个边界框。对于多行文本,首先按质心Y坐标分组。
实现说明:
Opencv允许您创建
直方图,但您可能可以使用类似以下内容的东西(对于我来说在类似任务上起作用):
def histogram(vals, th=4, bins=400):
hist = np.zeros(bins)
for y_center in vals:
bucket = int(round(y_center / 2.)) <-- change this "2."
hist[bucket-1] += 1
print("hist: ", hist)
hist = np.where(hist > th, hist, 0)
return hist
这里我的直方图只是一个有400个桶的数组(因为我的图像高度为800像素,所以每个桶捕捉两个像素,这就是“2.”来的地方)。Vals是每个片段质心的Y坐标(在构建此列表时,您可能希望忽略非常小的元素)。Th阈值只是为了去除一些噪音。您应该得到类似于以下内容的东西:
0,0,0,5,22,0,0,0,0,43,7,0,0,0
这个列表从上到下描述了每个位置有多少个片段。
现在我进行了另一次合并峰值的操作,将它们合并为一个单独的值(只需扫描数组并在其非零时求和,在第一个零时重置计数),得到类似于这样的结果 {y:count}:
{9:27, 20:50}
现在我知道我的文本分别位于y=9和y=20两行。现在或之前,您将每个片段分配到一行(在我的情况下再次使用8px阈值)。现在,您可以单独处理每一行,找到“单词”。顺便说一句,我也遇到了与断字相同的问题,这就是为什么我来这里寻找MSER的原因 :)。请注意,如果您找到单词的整个边界框,则此问题仅会发生在第一个/最后一个字母上:其他断开的字母仍然会落在单词框中。
这里是关于腐蚀/膨胀的参考
Here,但高斯模糊/th对我有用。
更新:我注意到这一行有些问题:
regions = mser.detectRegions(thresh)
我传入了已经进行阈值处理的图像(!?)。这对于聚合部分来说并不重要,但请记住,MSER部分没有按预期使用。