如何用OpenCV在P&ID图纸中检测方形符号?
检测P&ID图像中的方形符号(OpenCV实现)
问题背景
需要用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)
输入图像:
输出图像(仅标记垂直线):
解决方案
基于同一图像内方形尺寸一致的特点,结合霍夫线检测的垂直/水平线条,通过聚类和匹配来定位方形:
1. 核心思路
- 优化霍夫线参数,同时检测垂直和水平线条,通过角度过滤筛选目标线条
- 统计垂直边的x坐标差值,确定当前图像的方形边长
- 对垂直边按x坐标聚类,匹配对应的上下水平边,验证后绘制方形
2. 改进后代码
import cv2 as cv import numpy as np from collections import defaultdict 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, rho=1, theta=np.pi / 180, threshold=40, minLineLength=30, maxLineGap=5 ) vertical_lines = [] horizontal_lines = [] if linesP is not None: for line in linesP: x1, y1, x2, y2 = line[0] # 计算线条与水平轴的夹角 angle = np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi line_length = np.linalg.norm((x2 - x1, y2 - y1)) # 筛选垂直线(角度接近±90°,长度在合理范围) if abs(abs(angle) - 90) < 10 and 30 < line_length < 100: vertical_lines.append((x1, y1, x2, y2, line_length)) cv.line(img_display, (x1, y1), (x2, y2), (0, 0, 255), 1, cv.LINE_AA) # 筛选水平线(角度接近0°或180°,长度在合理范围) elif (abs(angle) < 10 or abs(abs(angle) - 180) < 10) and 30 < line_length < 100: horizontal_lines.append((x1, y1, x2, y2, line_length)) cv.line(img_display, (x1, y1), (x2, y2), (0, 255, 0), 1, cv.LINE_AA) # 统计垂直边x坐标差值,确定方形边长 x_coords = [] for x1, y1, x2, y2, _ in vertical_lines: x_coords.extend([x1, x2]) x_diffs = [] for i in range(len(x_coords) - 1): diff = abs(x_coords[i] - x_coords[i+1]) if 20 < diff < 100: # 限定边长范围 x_diffs.append(diff) if x_diffs: # 取出现频率最高的差值作为方形边长 square_side = max(set(x_diffs), key=x_diffs.count) print(f"检测到的方形边长:{square_side}") # 按x坐标聚类垂直边(误差容忍±5) vertical_groups = defaultdict(list) for line in vertical_lines: x1, y1, x2, y2, _ = line line_x = int((x1 + x2) / 2) # 按边长分组,确保同一组是同一方形的左右边 group_key = round(line_x / square_side) * square_side vertical_groups[group_key].append(line) # 遍历每组垂直边,匹配对应的水平边 for group_x, lines in vertical_groups.items(): if len(lines) != 2: continue # 一个方形对应两条垂直边 # 获取垂直边的y范围(方形的上下边界) y_min = min(min(y1, y2) for x1, y1, x2, y2, _ in lines) y_max = max(max(y1, y2) for x1, y1, x2, y2, _ in lines) # 查找匹配的顶部和底部水平边 top_line = None bottom_line = None for h_line in horizontal_lines: hx1, hy1, hx2, hy2, _ = h_line h_y = int((hy1 + hy2) / 2) h_x_min = min(hx1, hx2) h_x_max = max(hx1, hx2) # 匹配顶部水平边:y接近y_min,x覆盖方形左右边界 if abs(h_y - y_min) < 5 and h_x_min <= group_x and h_x_max >= group_x + square_side: top_line = h_line # 匹配底部水平边:y接近y_max,x覆盖方形左右边界 if abs(h_y - y_max) < 5 and h_x_min <= group_x and h_x_max >= group_x + square_side: bottom_line = h_line if top_line and bottom_line: # 绘制方形边框 cv.rectangle(img_display, (group_x, y_min), (group_x + square_side, y_max), (255, 0, 0), 2) # 保存结果 cv.imwrite('img_squares_detected.png', img_display)
3. 参数调整说明
threshold:霍夫线检测的阈值,值越高检测到的线条越精准minLineLength/maxLineGap:过滤过短或断裂的线条- 角度误差范围(
abs(abs(angle)-90) <10):根据图像线条的倾斜程度调整 - 边长范围(
20 < diff <100):根据实际方形尺寸调整
内容的提问来源于stack exchange,提问作者Aditya Patil
相关产品推荐
相关产品推荐

