合并 MSER 检测对象(OpenCV,Python)
Merge MSER detected objetcs (OpenCV, Python)
我正在处理这张图片作为来源:
正在应用下一个代码...
import cv2
import numpy as np
mser = cv2.MSER_create()
img = cv2.imread('C:\Users\Link\Desktop\test2.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
vis = img.copy()
regions, _ = mser.detectRegions(gray)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]
cv2.polylines(vis, hulls, 1, (0, 255, 0))
mask = np.zeros((img.shape[0], img.shape[1], 1), dtype=np.uint8)
for contour in hulls:
cv2.drawContours(mask, [contour], -1, (255, 255, 255), -1)
text_only = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('img', vis)
cv2.waitKey(0)
cv2.imshow('img', mask)
cv2.waitKey(0)
cv2.imshow('img', text_only)
cv2.waitKey(0)
cv2.imwrite('C:\Users\Link\Desktop\test_o\1.png', text_only)
...我得到这个作为结果(掩码):
问题是这样的:
如何合并成数字系列(157661546)中的单个对象the number 5
只要在mask图像中划分就可以了?
谢谢
看看here,好像是准确答案。
Here 相反,我的上述代码版本针对文本提取进行了微调(也有屏蔽)。
下面是上一篇文章的原代码,"ported"到python 3、opencv 3,添加了mser和bounding boxes。与我的版本的主要区别在于分组距离的定义方式:我的是面向文本的,而下面的是自由几何距离。
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: # <-- threshold
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) # wants black bg
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)
#cv2.drawContours(thresh,unified,-1,255,-1)
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 可能就可以正常工作。
------------------------
从这里开始有我的旧答案,在我找到上面的代码之前。无论如何我都会离开它,因为它描述了几种可能 easier/more 适用于某些场景的不同方法。
一个快速而肮脏的技巧是在 MSER 之前添加一个小的高斯模糊和一个高阈值(或者一些 dilute/erode 如果你喜欢花哨的东西)。在实践中,您只需将文本加粗,以填补小的空白。显然,您以后可以丢弃此版本并从原始版本裁剪。
否则,如果您的文本是成行的,您可以尝试检测平均线中心(例如制作 Y 坐标的直方图并找到峰值)。然后,对于每一行,寻找具有接近平均 X 的片段。如果文本是 noisy/complex.
,则非常脆弱
如果不需要拆分每个字母,获取整个单词的边界框,可能会更容易:只需根据片段之间的最大水平距离分组(使用 leftmost/rightmost 点轮廓)。然后使用每个组中最左边和最右边的框来找到整个边界框。对于多行文本,第一组按质心 Y 坐标。
实现说明:
Opencv 允许您创建 histograms 但您可能可以摆脱这样的事情(为我完成类似任务):
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:计数}:
{9:27, 20:50}
现在我知道我在 y=9 和 y=20 处有两个文本行。现在或之前,您将每个片段分配给在线(在我的情况下再次使用 8px 阈值)。现在您可以单独处理每一行,找到 "words"。顺便说一句,我遇到了与破损字母相同的问题,这就是我来这里寻找 MSER 的原因:)。请注意,如果您找到单词的整个边界框,此问题只会发生在 first/last 个字母上:其他损坏的字母无论如何都落在单词框中。
Here 是 erode/dilate 的参考,但高斯 blur/th 对我有用。
更新:我注意到这一行有问题:
regions = mser.detectRegions(thresh)
我传入已经过阈值的图像(!?)。这与聚合部分无关,但请记住,mser 部分未按预期使用。
我正在处理这张图片作为来源:
正在应用下一个代码...
import cv2
import numpy as np
mser = cv2.MSER_create()
img = cv2.imread('C:\Users\Link\Desktop\test2.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
vis = img.copy()
regions, _ = mser.detectRegions(gray)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]
cv2.polylines(vis, hulls, 1, (0, 255, 0))
mask = np.zeros((img.shape[0], img.shape[1], 1), dtype=np.uint8)
for contour in hulls:
cv2.drawContours(mask, [contour], -1, (255, 255, 255), -1)
text_only = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('img', vis)
cv2.waitKey(0)
cv2.imshow('img', mask)
cv2.waitKey(0)
cv2.imshow('img', text_only)
cv2.waitKey(0)
cv2.imwrite('C:\Users\Link\Desktop\test_o\1.png', text_only)
...我得到这个作为结果(掩码):
问题是这样的:
如何合并成数字系列(157661546)中的单个对象the number 5
只要在mask图像中划分就可以了?
谢谢
看看here,好像是准确答案。
Here 相反,我的上述代码版本针对文本提取进行了微调(也有屏蔽)。
下面是上一篇文章的原代码,"ported"到python 3、opencv 3,添加了mser和bounding boxes。与我的版本的主要区别在于分组距离的定义方式:我的是面向文本的,而下面的是自由几何距离。
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: # <-- threshold
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) # wants black bg
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)
#cv2.drawContours(thresh,unified,-1,255,-1)
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 可能就可以正常工作。
------------------------
从这里开始有我的旧答案,在我找到上面的代码之前。无论如何我都会离开它,因为它描述了几种可能 easier/more 适用于某些场景的不同方法。
一个快速而肮脏的技巧是在 MSER 之前添加一个小的高斯模糊和一个高阈值(或者一些 dilute/erode 如果你喜欢花哨的东西)。在实践中,您只需将文本加粗,以填补小的空白。显然,您以后可以丢弃此版本并从原始版本裁剪。
否则,如果您的文本是成行的,您可以尝试检测平均线中心(例如制作 Y 坐标的直方图并找到峰值)。然后,对于每一行,寻找具有接近平均 X 的片段。如果文本是 noisy/complex.
,则非常脆弱如果不需要拆分每个字母,获取整个单词的边界框,可能会更容易:只需根据片段之间的最大水平距离分组(使用 leftmost/rightmost 点轮廓)。然后使用每个组中最左边和最右边的框来找到整个边界框。对于多行文本,第一组按质心 Y 坐标。
实现说明:
Opencv 允许您创建 histograms 但您可能可以摆脱这样的事情(为我完成类似任务):
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:计数}:
{9:27, 20:50}
现在我知道我在 y=9 和 y=20 处有两个文本行。现在或之前,您将每个片段分配给在线(在我的情况下再次使用 8px 阈值)。现在您可以单独处理每一行,找到 "words"。顺便说一句,我遇到了与破损字母相同的问题,这就是我来这里寻找 MSER 的原因:)。请注意,如果您找到单词的整个边界框,此问题只会发生在 first/last 个字母上:其他损坏的字母无论如何都落在单词框中。
Here 是 erode/dilate 的参考,但高斯 blur/th 对我有用。
更新:我注意到这一行有问题:
regions = mser.detectRegions(thresh)
我传入已经过阈值的图像(!?)。这与聚合部分无关,但请记住,mser 部分未按预期使用。