【问题标题】:How to get rectangular box contours when there are overlapping distractions using OpenCV使用 OpenCV 有重叠干扰时如何获得矩形框轮廓
【发布时间】:2019-12-31 20:31:10
【问题描述】:

我在 python 中拼凑了一个快速算法,以从手写发票中获取输入框。

# some preprocessing
img = np.copy(orig_img)
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
img = cv2.GaussianBlur(img,(5,5),0)
_, img = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# get contours
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for i, cnt in enumerate(contours):
    approx = cv2.approxPolyDP(cnt, 0.01*cv2.arcLength(cnt,True), True)
    if len(approx) == 4:
        cv2.drawContours(orig_img, contours, i, (0, 255, 0), 2)

在本例中,由于笔迹越界,未能获得第二个。

请注意,这张照片可能是用手机拍摄的,所以纵横比可能有点搞笑。

那么,有哪些巧妙的方法可以解决我的问题?

作为奖励。这些盒子来自一个 A4 页面,上面还有很多其他的东西。你会推荐一种完全不同的方法来获取手写数字吗?

编辑

这可能很有趣。如果我不过滤 4 面多边形,我会得到轮廓,但它们会环绕手绘数字。也许有一种方法可以让轮廓具有像水一样的凝聚力,这样当它们靠近自己时就会被夹住?

进一步编辑

这是没有绘制边界框的原始图像

【问题讨论】:

  • 请同时添加原始图像,不要在其上绘制绿色边框。
  • 您可以尝试在 approPolyDp 之前裁剪掉一些凸面缺陷,但不确定是否适合您的案例...stackoverflow.com/questions/35226993/…

标签: python image opencv image-processing computer-vision


【解决方案1】:

这是一个潜在的解决方案:

  1. 获取二值图像。我们加载图像,转换为灰度,应用高斯模糊,然后应用 Otsu 阈值

  2. 检测水平线。我们创建一个水平内核并将检测到的水平线绘制到蒙版上

  3. 检测垂直线。我们创建一个垂直内核并将检测到的垂直线绘制到蒙版上

  4. 执行形态学开放。我们创建一个矩形内核并执行形态开放以消除噪声并分离任何连接的轮廓

  5. 查找轮廓,绘制矩形,提取 ROI。 我们查找轮廓并将边界矩形绘制到图像上


这是每个步骤的可视化:

二值图像

检测到绘制在蒙版上的水平线和垂直线

形态开放

结果

单独提取保存的 ROI

注意:要从每个 ROI 中仅提取手写数字/字母,请查看 Remove borders from image but keep text written on borders (preprocessing before OCR) 中的先前答案

代码

import cv2
import numpy as np

# Load image, grayscale, blur, Otsu's threshold
image = cv2.imread('1.png')
original = image.copy()
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Find horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(mask, [c], -1, (255,255,255), 3)

# Find vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(mask, [c], -1, (255,255,255), 3)

# Morph open
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)

# Draw rectangle and save each ROI
number = 0
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
    ROI = original[y:y+h, x:x+w]
    cv2.imwrite('ROI_{}.png'.format(number), ROI)
    number += 1

cv2.imshow('thresh', thresh)
cv2.imshow('mask', mask)
cv2.imshow('opening', opening)
cv2.imshow('image', image)
cv2.waitKey()

【讨论】:

【解决方案2】:

由于正方形的直线很直,所以最好使用霍夫变换:

1- 使图像灰度化,然后对其进行 Otsu 阈值,然后反转二值图像

2- 做霍夫变换 (HoughLinesP) 并在新图像上画线

3- 使用 findContoursdrawContours,使 3 roi 干净

4- 稍微腐蚀最终图像以使盒子更整洁

我用 C++ 编写了代码,它很容易转换为 python:

Mat img = imread("D:/1.jpg", 0);
threshold(img, img, 0, 255, THRESH_OTSU);
imshow("Binary image", img);

img = 255 - img;
imshow("Reversed binary image", img);

Mat img_1 = Mat::zeros(img.size(), CV_8U);
Mat img_2 = Mat::zeros(img.size(), CV_8U);

vector<Vec4i> lines;
HoughLinesP(img, lines, 1, 0.1, 95, 10, 1);
for (size_t i = 0; i < lines.size(); i++)
    line(img_1, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]), 
        Scalar(255, 255, 255), 2, 8);

imshow("Hough Lines", img_1);

vector<vector<Point>> contours;
findContours(img_1,contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
for (int i = 0; i< contours.size(); i++)
    drawContours(img_2, contours, i, Scalar(255, 255, 255), -1);

imshow("final result after drawcontours", img_2);    waitKey(0);

【讨论】:

【解决方案3】:

感谢分享解决方案的人。我最终选择了一条略有不同的道路。

  1. 灰度、高斯模糊、大津阈值
  2. 获取轮廓
  3. 按纵横比和范围过滤轮廓
  4. 返回轮廓的最小垂直边界框。
  5. 删除所有封装较小边界框的边界框(因为您会得到两个框,一个用于内部轮廓,一个用于外部)。

如果有人感兴趣,这里是代码(除了第 5 步 - 那只是基本的 numpy 操作)

orig_img = cv2.imread('example0.jpg')

img = np.copy(orig_img)
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
img = cv2.GaussianBlur(img,(5,5),0)
_, img = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

boxes = list()

for i, cnt in enumerate(contours):
    x,y,w,h = cv2.boundingRect(cnt)
    aspect_ratio = float(w)/h
    area = cv2.contourArea(cnt)
    rect_area = w*h
    extent = float(area)/rect_area
    if abs(aspect_ratio - 1) < 0.1 and extent > 0.7:
        boxes.append((x,y,w,h))

下面是从原始图像中切出边界框时出现的示例。

【讨论】:

    猜你喜欢
    • 2019-12-07
    • 2019-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-11
    • 2012-05-10
    • 2021-09-01
    相关资源
    最近更新 更多