手绘直线检测与直角连接修复技术求助
手绘直线规整化与直角连接解决方案
问题描述
我有一张手绘直线的图片,目标是将这些直线规整化并合理连接,重新绘制到新的白色背景图中。先后使用了HoughLineP和cv2.createLineSegmentDetector,但在实现直线直角连接时效果很差。
原始实现代码
lsd = cv2.createLineSegmentDetector(0) image = cv2.imread("Image/IMG_8764.jpg", 0) lines, width, prec, nfa = lsd.detect(image) print(len(lines)) white_background = np.ones_like(image) * 0 for line in lines: x1, y1, x2, y2 = map(int, line[0]) cv2.line(white_background, (x1, y1), (x2, y2), (255, 255, 255), 3)
尝试的解决方案代码
def angle_cos(p0, p1, p2): d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float') return abs( np.dot(d1, d2) / np.sqrt( np.dot(d1, d1)*np.dot(d2, d2) ) ) def makebin(gray): bin = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 2) return cv2.bitwise_not(bin) def find_squares(img): img = cv2.GaussianBlur(img, (5, 5), 0) squares = [] points = [] for gray in cv2.split(img): bin = makebin(gray) contours, hierarchy = cv2.findContours(bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) corners = cv2.goodFeaturesToTrack(gray,len(contours)*4,0.2,15) cv2.cornerSubPix(gray,corners,(9,9),(-1,-1),(cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS,10, 0.1)) for cnt in contours: cnt_len = cv2.arcLength(cnt, True) if len(cnt) >= 4 and cv2.contourArea(cnt) > 500: rect = cv2.boundingRect(cnt) if rect not in squares: squares.append(rect) return squares, corners, contours if __name__ == '__main__': for fn in glob('Test.jpg'): img = cv2.imread(fn) squares, corners, contours = find_squares(img) for p in corners: cv2.circle(img, (int(p[0][0]), int(p[0][1])), 3, (0, 0, 255), 2) squares = sorted(squares,key=itemgetter(1,0,2,3)) areas = [] moments = [] centers = [] for s in squares: areas.append(s[2]*s[3]) cv2.rectangle( img, (s[0],s[1]),(s[0]+s[2],s[1]+s[3]),(0,255,0),5) for c in contours: moments.append(cv2.moments(np.array(c))) for m in moments: if m["m00"] != 0: centers.append((int(m["m10"] // m["m00"]), int(m["m01"] // m["m00"]))) for cent in centers: cv2.circle(img, (cent[0],cent[1]), 3, (0,255,0),2) cv2.imshow('squares', ResizeWithAspectRatio(img,800,800)) ch = 0xFF & cv2.waitKey() if ch == 27: break cv2.destroyAllWindows()
解决方案思路与实现
核心逻辑
单纯用LSD或Hough变换只能提取原始手绘线段,无法处理直线方向规整和断点连接。解决问题的关键是:先获取精准的角点(交点),再将线段对齐到正交方向(水平/垂直),最后基于角点完成线段的连接与去重。
步骤1:图像预处理与精准角点检测
先对图像降噪、二值化,再用亚像素级角点检测获取手绘直线的交点,为后续规整提供基准:
import cv2 import numpy as np # 预处理:降噪+二值化 def preprocess_image(img_path): img = cv2.imread(img_path, 0) blur = cv2.GaussianBlur(img, (3,3), 0) # 自适应二值化适配手绘的不均匀光照 binary = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) return binary # 亚像素级角点检测,提升精度 def get_precise_corners(binary_img): # 初始角点检测 corners = cv2.goodFeaturesToTrack(binary_img, maxCorners=100, qualityLevel=0.01, minDistance=10) # 亚像素优化 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners = cv2.cornerSubPix(binary_img, corners, (5,5), (-1,-1), criteria) return np.int0(corners)
步骤2:直线方向规整
将提取的原始直线校正为水平或垂直方向,对齐到最近的角点坐标:
def normalize_lines(lines, corners): normalized_lines = [] # 提取所有角点的x、y坐标,用于快速对齐 corner_xs = [c[0][0] for c in corners] corner_ys = [c[0][1] for c in corners] for line in lines: x1, y1, x2, y2 = map(int, line[0]) # 计算直线角度,判断是水平还是垂直 angle = np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi if abs(angle) < 45 or abs(angle - 180) < 45: # 水平直线:统一y坐标为最近的角点y值 y_mean = (y1 + y2) // 2 nearest_y = min(corner_ys, key=lambda y: abs(y - y_mean)) normalized_lines.append([(min(x1, x2), nearest_y), (max(x1, x2), nearest_y)]) else: # 垂直直线:统一x坐标为最近的角点x值 x_mean = (x1 + x2) // 2 nearest_x = min(corner_xs, key=lambda x: abs(x - x_mean)) normalized_lines.append([(nearest_x, min(y1, y2)), (nearest_x, max(y1, y2))]) return normalized_lines
步骤3:线段连接与去重
将规整后的线段端点延伸到角点,同时去除重复线段:
def connect_lines(normalized_lines, corners, threshold=5): # 角点转为集合,方便快速查找 corner_set = set([(c[0][0], c[0][1]) for c in corners]) final_lines = [] for line in normalized_lines: (x1, y1), (x2, y2) = line # 若端点不是角点,延伸到最近的角点 if (x1, y1) not in corner_set: nearest = min(corner_set, key=lambda p: np.hypot(p[0]-x1, p[1]-y1)) x1, y1 = nearest if (x2, y2) not in corner_set: nearest = min(corner_set, key=lambda p: np.hypot(p[0]-x2, p[1]-y2)) x2, y2 = nearest # 去重:用排序后的线段元组判断是否重复 line_tuple = tuple(sorted([(x1, y1), (x2, y2)])) if line_tuple not in final_lines: final_lines.append(line_tuple) return final_lines
完整执行代码
if __name__ == "__main__": img_path = "Image/IMG_8764.jpg" # 预处理图像 binary_img = preprocess_image(img_path) # 获取精准角点 corners = get_precise_corners(binary_img) # LSD提取原始直线 lsd = cv2.createLineSegmentDetector(0) lines, _, _, _ = lsd.detect(binary_img) # 规整直线+连接去重 normalized_lines = normalize_lines(lines, corners) final_lines = connect_lines(normalized_lines, corners) # 在白色背景绘制结果 h, w = binary_img.shape white_bg = np.ones((h, w), dtype=np.uint8) * 255 for p1, p2 in final_lines: cv2.line(white_bg, p1, p2, (0, 0, 0), 2) # 显示与保存 cv2.imshow("规整化直线结果", white_bg) cv2.imwrite("normalized_lines.jpg", white_bg) cv2.waitKey(0) cv2.destroyAllWindows()
适配调整建议
- 如果手绘包含非正交角度(如45°),可修改角度判断逻辑,增加对应角度的规整分支
- 角点检测的
qualityLevel和minDistance参数需根据图像分辨率、手绘线条粗细调整 - 线段延伸的
threshold值控制端点与角点的匹配距离,需适配图像实际情况
内容的提问来源于stack exchange,提问作者lee
相关产品推荐
相关产品推荐

