我正在尝试使用 OpenCV 检测 P&ID(图表)图像文件中的方形符号。 我尝试遵循使用轮廓的教程,但该方法似乎不适用于此类图表图像。使用霍夫线,我可以标记这些正方形的垂直边缘,但我不确定如何使用这些边缘检测正方形。图像中的所有正方形都具有相同的尺寸,但不同图像的尺寸可能不同,因此模板匹配对我不起作用。
我使用霍夫线的代码:
import cv2 as cv
import numpy as np
import math
img = cv.imread('test_img.jpg')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img_display = img.copy()
ret,thresh = cv.threshold(img_gray,250,255,cv.THRESH_BINARY)
image_inverted = cv.bitwise_not(thresh)
linesP = cv.HoughLinesP(image_inverted, 1, np.pi / 1, 50, None, 50, 2)
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
length = math.sqrt((l[2] - l[0])**2 + (l[3] - l[1])**2)
if length < 100:
cv.line(img_display, (l[0], l[1]), (l[2], l[3]), (0,0,255), 1, cv.LINE_AA)
cv.imwrite('img_display.png', img_display)
输入图片:
输出图像:
在上面的代码中,我将其设置为仅检测垂直线,因为它无法可靠地检测水平线。
如果您知道线条是水平的或垂直的,您可以通过组合侵蚀和扩张来过滤它们(文档描述了它的工作原理)。
分离水平线和垂直线后,您可以按大小过滤它们。最后,您可以填充所有剩余的闭合轮廓,并再次使用侵蚀/删除来提取更大的形状。
这比使用霍夫线变换更可靠,并且可以让您更好地控制到底提取的内容。
这是一个演示:
import numpy as np
import cv2 as cv
min_length = 29
max_length = 150
# erode and dilate with rectangular kernel of given dimensions
def erode_dilate(image, dim):
kernel = cv.getStructuringElement(cv.MORPH_RECT, dim)
result = cv.erode(image, kernel)
result = cv.dilate(result, kernel)
return result
# get contours and filter by max_size
def filter_contours(image, max_size):
contours, _ = cv.findContours(image, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
dims = [(cnt, cv.boundingRect(cnt)) for cnt in contours]
contours = [cnt for cnt, (x, y, w, h) in dims if w <= max_size and h <= max_size]
return contours
# read image and get inverted threshold mask
img = cv.imread('test_img.jpg')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thres = cv.threshold(img_gray, 250, 255, cv.THRESH_BINARY_INV)
# extract horizontal lines
thres_h = erode_dilate(thres, (min_length, 1))
cv.imshow("horizontal", thres_h)
# extract vertical lines
thres_v = erode_dilate(thres, (1, min_length))
cv.imshow("vertical", thres_v)
# filter lines by max_length and draw them back to res
res = np.zeros_like(thres)
cntrs_h = filter_contours(thres_h, max_length)
cv.drawContours(res, cntrs_h, -1, 255, cv.FILLED)
cntrs_v = filter_contours(thres_v, max_length)
cv.drawContours(res, cntrs_v, -1, 255, cv.FILLED)
cv.imshow("filtered horizontal + vertical", res)
# fill remaining shapes
cntrs = filter_contours(res, max_length)
for c in cntrs:
cv.drawContours(res, [c], -1, 255, cv.FILLED)
cv.imshow("filled", res)
# extract larger shapes
res = erode_dilate(res, (min_length, min_length))
cv.imshow("squares", res)
# draw contours of detected shapes on original image
cntrs = filter_contours(res, max_length)
cv.drawContours(img, cntrs, -1, (0, 0, 255), 2)
cv.imshow("output", img)
cv.waitKey(-1)
cv.destroyAllWindows()
输出:
效果非常好!如何提取方块内的文字?尝试了 pytesseract 但没有取得太大成功。