如何基于角度连接Julia中带标签的图像线条?
可行实现思路
1. 提取每个标签对应的线条像素坐标
先把每个标签对应的所有像素坐标提取出来,形成点集,方便后续处理:
# 假设thinned是二值图像,labels是标签矩阵 label_list = unique(labels)[2:end] # 剔除背景标签0 line_points = Dict() for lbl in label_list coords = findall(x -> x == lbl, labels) # 转换为(x,y)坐标(Julia矩阵是行优先,CartesianIndex的第一个元素是行即y,第二个是列即x) line_points[lbl] = [(c[2], c[1]) for c in coords] end
2. 计算每条线条的主方向角度
因为线条可能是非直线,用**主成分分析(PCA)**求主方向更准确,能反映线条的整体走向:
using LinearAlgebra function get_line_angle(points) # 转换为矩阵格式,每行对应一个点的坐标 mat = hcat([p[1] for p in points], [p[2] for p in points])' # 坐标去均值,消除位置影响 centered = mat .- mean(mat, dims=2) # 计算协方差矩阵 cov_mat = cov(centered') # 求解特征值和特征向量,最大特征值对应主方向 eigvals, eigvecs = eigen(cov_mat) main_vec = eigvecs[:, argmax(eigvals)] # 弧度转角度,统一到0-180°范围(避免方向相反的角度被误判) angle_rad = atan(main_vec[2], main_vec[1]) angle_deg = rad2deg(angle_rad) return angle_deg < 0 ? angle_deg + 180 : angle_deg end # 批量计算所有线条的角度 line_angles = Dict(lbl => get_line_angle(points) for (lbl, points) in line_points)
3. 筛选角度相近的线条对
设定角度阈值(比如5°),遍历所有线条对,判断角度差是否在阈值范围内:
angle_threshold = 5.0 # 可根据实际图像调整 similar_pairs = [] lbls = collect(keys(line_angles)) for i in 1:length(lbls) for j in i+1:length(lbls) angle_diff = abs(line_angles[lbls[i]] - line_angles[lbls[j]]) # 处理180°循环的情况,比如178°和2°的实际差是4° angle_diff = min(angle_diff, 180 - angle_diff) if angle_diff <= angle_threshold push!(similar_pairs, (lbls[i], lbls[j])) end end end
4. 插值连接角度相近的线条
4.1 提取线条端点
通过邻域像素计数识别端点,闭合线条则取离中心最远的两个点:
function get_line_endpoints(points, thinned_img) endpoints = [] for (x,y) in points # 统计3x3邻域内的非零像素数 y_range = max(1,y-1):min(size(thinned_img,1),y+1) x_range = max(1,x-1):min(size(thinned_img,2),x+1) neighbors = thinned_img[y_range, x_range] count = sum(neighbors) # 端点只有1个相邻像素(自身为1,所以总和是2) if count == 2 push!(endpoints, (x,y)) end end # 处理闭合线条无端点的情况 if length(endpoints) < 2 center = mean(points) # 取离中心最远的两个点作为"端点" sorted_points = sort(points, by=p -> norm(p .- center), rev=true) endpoints = sorted_points[1:2] end return endpoints end # 提取所有线条的端点 line_endpoints = Dict(lbl => get_line_endpoints(points, thinned) for (lbl, points) in line_points)
4.2 插值连接端点
找到两条线条距离最近的端点对,用线性插值生成中间点并标记到图像:
function connect_lines(p1, p2, num_points=10) # 生成线性插值点 x_vals = range(p1[1], p2[1], length=num_points) y_vals = range(p1[2], p2[2], length=num_points) return collect(zip(x_vals, y_vals)) end # 对相似线条对进行连接 connected_img = copy(thinned) for (lbl1, lbl2) in similar_pairs eps1 = line_endpoints[lbl1] eps2 = line_endpoints[lbl2] # 筛选距离最近的端点组合 min_dist = Inf best_pair = (eps1[1], eps2[1]) for e1 in eps1 for e2 in eps2 dist = norm(e1 .- e2) if dist < min_dist min_dist = dist best_pair = (e1, e2) end end end # 生成插值点并更新图像 interp_points = connect_lines(best_pair[1], best_pair[2]) for (x,y) in interp_points # 转换为矩阵索引并标记 connected_img[round(Int,y), round(Int,x)] = 1 end end
额外注意事项
- 若线条弯曲程度大,可考虑分段计算角度,或用霍夫变换检测直线段后再求角度
- 角度阈值需根据实际场景调整,避免误连或漏连
- 插值方式可替换为贝塞尔曲线等,实现更平滑的连接效果
内容的提问来源于stack exchange,提问作者F612
相关产品推荐
相关产品推荐

