合并MSER检测到objetcs(OpenCV,Python)

时间:2017-11-30 15:40:02

标签: python opencv mser

我正在处理此图像作为来源:

src

应用下一个代码......

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)

...我得到了这个结果(面具):

o1

问题是:

如果在数字系列(157661546)中合并为单个对象the number 5,只要它在掩码图像中分割?

由于

1 个答案:

答案 0 :(得分:4)

看看here,这似乎是确切的答案。

Here而是我的上述代码版本经过精心调整以进行文本提取(也可以屏蔽)。

下面是上一篇文章的原始代码,“移植”到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:      # <-- 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()

示例输出(黄色斑点低于二进制阈值转换,因此忽略它)。红色:原始轮廓,绿色:统一轮廓,蓝色:边界框。

Sample output

可能没有必要使用MSER,因为简单的findContours可以正常工作。

<强> ------------------------

在我找到上述代码之前,从这里开始有我的旧答案。无论如何,我要离开它,因为它描述了一些可能更容易/更适合某些场景的不同方法。

一个快速而肮脏的技巧是在MSER之前添加一个小的高斯模糊和一个高阈值(如果你喜欢花哨的东西,可以用一些稀释/腐蚀)。在实践中,您只需使文本更大胆,以填补小间隙。显然,您可以稍后丢弃此版本并从原始版本裁剪。

否则,如果文本是行,则可以尝试检测平均线中心(例如,绘制Y坐标的直方图并找到峰值)。然后,对于每一行,寻找具有接近平均值X的片段。如果文本是嘈杂/复杂的话,则非常脆弱。

如果您不需要拆分每个字母,获取整个单词的边界框可能会更容易:只需根据片段之间的最大水平距离(使用轮廓的最左侧/最右侧点)分组。然后使用每个组中最左边和最右边的框来查找整个边界框。对于多行文本,第一组按质心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个桶的数组(我的图像是800px高,因此每个桶捕获两个像素,即“2.”来自的位置)。 Vals是每个片段的质心的Y坐标(在构建此列表时,您可能希望忽略非常小的元素)。阈值只是为了消除一些噪音。你应该得到这样的东西:

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是侵蚀/扩张的参考,但是高斯模糊/为我工作。

更新:我注意到这一行有问题:

regions = mser.detectRegions(thresh)

我传入已经过阈值的图像(!?)。这与聚合部分无关,但请记住,mser部分未按预期使用。