You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

手绘直线检测与直角连接修复技术求助

手绘直线规整化与直角连接解决方案

问题描述

我有一张手绘直线的图片,目标是将这些直线规整化并合理连接,重新绘制到新的白色背景图中。先后使用了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 01:19:53