如何使用OpenCV/Python连接二值图像中的断裂线条
断裂平行线条的连接方案

图像中平行线条存在断裂,常规形态学操作连接效果不佳,由于线条方向一致,单靠方向计算无法解决问题。以下是几种可行的Python实现方案:
用户当前代码(补全缺失导入)
import cv2 import numpy as np from math import atan2, sqrt, pi img = cv2.imread('mask.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为二值图像 _, bw = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 计算轮廓 contours, _ = cv2.findContours(bw, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) def get_orientation(pts, img): sz = len(pts) data_pts = np.empty((sz, 2), dtype=np.float64) for i in range(data_pts.shape[0]): data_pts[i,0] = pts[i,0,0] data_pts[i,1] = pts[i,0,1] # 执行PCA分析 mean = np.empty((0)) mean, eigenvectors, eigenvalues = cv2.PCACompute2(data_pts, mean) # 存储目标中心 cntr = (int(mean[0,0]), int(mean[0,1])) cv2.circle(img, cntr, 3, (255, 0, 255), 2) p1 = (cntr[0] + 0.02 * eigenvectors[0,0] * eigenvalues[0,0], cntr[1] + 0.02 * eigenvectors[0,1] * eigenvalues[0,0]) p2 = (cntr[0] - 0.02 * eigenvectors[1,0] * eigenvalues[1,0], cntr[1] - 0.02 * eigenvectors[1,1] * eigenvalues[1,0]) draw_axis(img, cntr, p1, (0, 150, 0), 1) draw_axis(img, cntr, p2, (200, 150, 0), 5) angle = atan2(eigenvectors[0,1], eigenvectors[0,0]) # 弧度制方向角 return angle def draw_axis(img, p_, q_, colour, scale): p = list(p_) q = list(q_) angle = atan2(p[1] - q[1], p[0] - q[0]) # 弧度制角度 hypotenuse = sqrt((p[1] - q[1]) * (p[1] - q[1]) + (p[0] - q[0]) * (p[0] - q[0])) # 按比例延长箭头 q[0] = p[0] - scale * hypotenuse * cos(angle) q[1] = p[1] - scale * hypotenuse * sin(angle) cv2.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv2.LINE_AA) # 绘制箭头钩 p[0] = q[0] + 9 * cos(angle + pi / 4) p[1] = q[1] + 9 * sin(angle + pi / 4) cv2.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv2.LINE_AA) p[0] = q[0] + 9 * cos(angle - pi / 4) p[1] = q[1] + 9 * sin(angle - pi / 4) cv2.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv2.LINE_AA) for i,c in enumerate(contours): # 每个轮廓的面积 area = cv2.contourArea(c) # 计算每个形状的方向 orrr = get_orientation(c,img) print(orrr)
方案1:定向膨胀(自定义结构元素)
通过定制方向匹配的结构元素,实现仅在线条延伸方向上的膨胀,避免无差别扩张:
水平线条场景
import cv2 import numpy as np img = cv2.imread('mask.jpg', 0) _, bw = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 创建水平结构元素:长度根据断裂间隙调整,宽度保持与线条一致 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 1)) # 定向膨胀连接断裂 dilated = cv2.dilate(bw, kernel, iterations=1) # 可选:腐蚀还原线条粗细(避免过度膨胀) eroded = cv2.erode(dilated, cv2.getStructuringElement(cv2.MORPH_RECT, (1,1)), iterations=1) cv2.imshow('Connected Lines', eroded) cv2.waitKey(0) cv2.destroyAllWindows()
倾斜线条场景
如果线条是倾斜的,旋转结构元素适配方向:
import cv2 import numpy as np img = cv2.imread('mask.jpg', 0) _, bw = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 定义结构元素尺寸与旋转角度 kernel_size = (15, 3) angle = 45 # 替换为实际线条角度 # 创建矩形核并旋转 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, kernel_size) center = (kernel_size[0]//2, kernel_size[1]//2) rot_matrix = cv2.getRotationMatrix2D(center, angle, 1.0) kernel = cv2.warpAffine(kernel, rot_matrix, kernel_size, flags=cv2.INTER_NEAREST) # 定向膨胀 dilated = cv2.dilate(bw, kernel, iterations=1) cv2.imshow('Rotated Dilated Lines', dilated) cv2.waitKey(0) cv2.destroyAllWindows()
方案2:霍夫直线检测重绘
利用霍夫直线检测识别平行线条的整体走向,直接绘制完整线条:
import cv2 import numpy as np img = cv2.imread('mask.jpg', 0) _, bw = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 边缘检测提升霍夫检测精度 edges = cv2.Canny(bw, 50, 150, apertureSize=3) # 霍夫直线检测:阈值根据图像调整 lines = cv2.HoughLines(edges, 1, np.pi/180, threshold=100) result = cv2.cvtColor(bw, cv2.COLOR_GRAY2BGR) for line in lines: rho, theta = line[0] a = np.cos(theta) b = np.sin(theta) x0 = a * rho y0 = b * rho # 延长线条至图像边界 x1 = int(x0 + 1000 * (-b)) y1 = int(y0 + 1000 * (a)) x2 = int(x0 - 1000 * (-b)) y2 = int(y0 - 1000 * (a)) cv2.line(result, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow('Hough Connected Lines', result) cv2.waitKey(0) cv2.destroyAllWindows()
方案3:端点配对连接
提取所有线段端点,匹配同方向近距离的端点并连接:
import cv2 import numpy as np from math import hypot img = cv2.imread('mask.jpg', 0) _, bw = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) contours, _ = cv2.findContours(bw, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 提取所有轮廓的端点 endpoints = [] for cnt in contours: pt1 = cnt[0][0] pt2 = cnt[-1][0] endpoints.append((pt1[0], pt1[1])) endpoints.append((pt2[0], pt2[1])) result = cv2.cvtColor(bw, cv2.COLOR_GRAY2BGR) threshold_dist = 20 # 间隙阈值,根据实际情况调整 y_tolerance = 5 # 平行线条的y坐标容忍度 visited = [False] * len(endpoints) for i in range(len(endpoints)): if visited[i]: continue x1, y1 = endpoints[i] min_dist = float('inf') match_idx = -1 # 寻找同方向近距离端点 for j in range(i+1, len(endpoints)): if visited[j]: continue x2, y2 = endpoints[j] dist = hypot(x2 - x1, y2 - y1) if abs(y2 - y1) < y_tolerance and dist < min_dist and dist < threshold_dist: min_dist = dist match_idx = j # 连接配对端点 if match_idx != -1: cv2.line(result, (x1, y1), endpoints[match_idx], (0, 0, 255), 2) visited[i] = True visited[match_idx] = True cv2.imshow('Endpoint Connected Lines', result) cv2.waitKey(0) cv2.destroyAllWindows()
内容的提问来源于stack exchange,提问作者user6364126
相关产品推荐
相关产品推荐

