图像中的正方形检测

32

我正在尝试检测所有方形的骰子图像,以便可以将它们单独裁剪并用于OCR。 以下是原始图像:

image3 image4

这是我的代码,但其中有一些方块缺失。

def find_squares(img):
    img = cv2.GaussianBlur(img, (5, 5), 0)
    squares = []
    for gray in cv2.split(img):
        for thrs in range(0, 255, 26):
            if thrs == 0:
                bin = cv2.Canny(gray, 0, 50, apertureSize=5)
                bin = cv2.dilate(bin, None)
            else:
                _retval, bin = cv2.threshold(gray, thrs, 255, cv2.THRESH_BINARY)
            bin, contours, _hierarchy = cv2.findContours(bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
            for cnt in contours:
                cnt_len = cv2.arcLength(cnt, True)
                cnt = cv2.approxPolyDP(cnt, 0.02*cnt_len, True)
                if len(cnt) == 4 and cv2.contourArea(cnt) > 1000 and cv2.isContourConvex(cnt):
                    cnt = cnt.reshape(-1, 2)
                    max_cos = np.max([angle_cos( cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4] ) for i in range(4)])
                    #print(cnt)
                    a = (cnt[1][1] - cnt[0][1])

                    if max_cos < 0.1 and a < img.shape[0]*0.8:

                        squares.append(cnt)
    return squares

dice = cv2.imread('img1.png')
squares = find_squares(dice)
cv2.drawContours(dice, squares, -1, (0, 255, 0), 3)

以下是输出图像:

Image1Image2

根据我的分析,在骰子周围的边缘缺失Canny算子后,有些方块丢失了,因为骰子和背景之间存在平滑的强度过渡。

假设我们有一个约束条件,即在正方形网格模式(5*5)中始终有25个骰子,我们能否基于已识别的正方形预测缺失的正方形位置?或者我们可以修改上述算法以进行正方形检测?


请说明您的程序在查找其他正方形时出现了什么问题。它是否有问题识别某些边缘?图像边界是否使其混淆?在某些情况下,它是否无法连接相邻的边缘?我们希望您提供合理的调试过程;仅仅告诉我们“它不起作用”并不是一个问题的具体说明。 - Prune
@Prune 感谢您的建议,我已经添加了关于这个挑战的分析。 - flamelite
快来看看并帮忙 https://stackoverflow.com/q/77386078/19874226 - undefined
2个回答

58
  1. 锐化方形边缘。 加载图像,转换为灰度图像,使用中值模糊平滑,再使用锐化增强边缘。

  2. 获取二进制图像并去除噪声。 我们使用阈值处理获得黑/白二进制图像。根据图像的不同,Otsu阈值处理自适应阈值处理会起作用。从这里我们创建一个矩形核并执行形态学变换以去除噪声并增强正方形轮廓。

  3. 检测和提取正方形。 接下来,我们查找轮廓并使用最小/最大阈值面积进行过滤。通过筛选的任何轮廓都将是我们的正方形,因此为了提取每个ROI,我们获取边界矩形坐标,使用Numpy切片进行裁剪,并保存每个正方形图像。


使用通用锐化核其他核可以在此处找到,通过cv2.filter2D()来锐化图像。

enter image description here

现在获取二值图像的阈值

enter image description here

有一些噪点,为了去除它们,我们进行形态学操作。

enter image description here

接下来,找到轮廓并使用cv2.contourArea()函数进行过滤,可以设置最小/最大阈值。

enter image description here

我们可以使用Numpy切片来裁剪每个所需的正方形区域,并像这样保存每个ROI。
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h, x:x+w]
cv2.imwrite('ROI_{}.png'.format(image_number), ROI)

enter image description here

import cv2
import numpy as np

# Load image, grayscale, median blur, sharpen image
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 5)
sharpen_kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
sharpen = cv2.filter2D(blur, -1, sharpen_kernel)

# Threshold and morph close
thresh = cv2.threshold(sharpen, 160, 255, cv2.THRESH_BINARY_INV)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)

# Find contours and filter using threshold area
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

min_area = 100
max_area = 1500
image_number = 0
for c in cnts:
    area = cv2.contourArea(c)
    if area > min_area and area < max_area:
        x,y,w,h = cv2.boundingRect(c)
        ROI = image[y:y+h, x:x+w]
        cv2.imwrite('ROI_{}.png'.format(image_number), ROI)
        cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
        image_number += 1

cv2.imshow('sharpen', sharpen)
cv2.imshow('close', close)
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()

3
非常感谢您的出色回答。我认为循环中的这一行 ROI = image[y:y+h, x:x+h] 应该改为 ROI = image[y:y+h, x:x+w],对吗? - Dgan
@Ganesh_Devlekar,是的,你说得对。谢谢。我已经更新了它。 - nathancy
3
哇,这真是惊人的。 - Yasir Hantoush
1
这是一个非常深入和惊人的答案。太棒了,先生。 - Sharpienero
这不是一个答案,而是一个完整的教程。太棒了! - Cagy79

0

那个额外的信息绝对是黄金般珍贵的。是的,给定一个5x5的骰子矩阵,你可以很好地确定位置。你能够识别的骰子可以给你中心、大小和方向。只需沿着两个轴继续这些模式即可。对于第二次扫描,在你期望找到骰子边缘的每个“感兴趣的区域”中增加对比度(永不言败!)。你知道边缘将在几个像素内:只需衰减图像直到你识别出这些边缘。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接