如何从带曲线及不光滑边缘的线条骨架中获取线条宽度?
弯曲不光滑线条宽度估算的Julia实现方案
核心步骤与代码实现
1. 图像预处理(降噪+二值化)
先对原始图像做灰度转换、二值化和降噪,减少后续处理的干扰:
using Images, ImageMorphology, ImageFiltering # 读取并预处理图像 img = load("你的图像路径.png") gray_img = Gray.(img) # Otsu法自动二值化 binary_img = binarize(gray_img, Otsu()) # 3x3中值滤波降噪 denoised_img = imfilter(binary_img, Kernel.median((3,3)))
2. 提取线条骨架
用形态学骨架提取函数得到线条的中心骨架:
skeleton = skeletonize(denoised_img)
3. 计算骨架点的正交方向
通过Sobel算子计算骨架的梯度,推导正交方向(垂直于线条切线的方向):
# 计算Sobel梯度 sobel_x = Kernel.sobel()[1] sobel_y = Kernel.sobel()[2] grad_x = imfilter(convert(Array{Float64}, skeleton), sobel_x) grad_y = imfilter(convert(Array{Float64}, skeleton), sobel_y) # 正交方向角度(梯度方向偏移π/2) ortho_angles = atan.(grad_y, grad_x) .+ π/2
4. 沿正交方向测量宽度
遍历每个骨架点,沿正交正负方向搜索边缘,记录两侧距离并求和:
# 获取所有骨架点坐标 skeleton_points = findall(skeleton .== 1) widths = Float64[] for point in skeleton_points x, y = Tuple(point) angle = ortho_angles[x, y] # 正方向搜索边缘 dist_pos = 0 curr_x, curr_y = x, y while true curr_x += round(Int, cos(angle)) curr_y += round(Int, sin(angle)) # 边界检查 if curr_x < 1 || curr_x > size(img,1) || curr_y <1 || curr_y > size(img,2) break end if denoised_img[curr_x, curr_y] == 0 break end dist_pos += 1 end # 负方向搜索边缘 dist_neg = 0 curr_x, curr_y = x, y while true curr_x -= round(Int, cos(angle)) curr_y -= round(Int, sin(angle)) if curr_x <1 || curr_x > size(img,1) || curr_y <1 || curr_y > size(img,2) break end if denoised_img[curr_x, curr_y] == 0 break end dist_neg +=1 end # 总宽度=两侧距离+自身点 push!(widths, dist_pos + dist_neg + 1) end # 计算平均宽度 avg_width = mean(widths) println("估算平均线条宽度:", avg_width)
5. 简化替代方案(距离变换法)
如果不需要逐点正交搜索,可用距离变换快速估算:
# 对背景做距离变换,前景点到最近背景的距离 dist_transform = distance_transform(1 .- denoised_img) # 骨架点的距离值乘以2即为近似宽度 skeleton_dist = dist_transform[skeleton_points] avg_width_dist = mean(skeleton_dist) * 2 println("距离变换法估算平均宽度:", avg_width_dist)
优化建议
- 若骨架局部噪声大,可先对二值图像做
dilate再erode的开闭运算,平滑边缘后再提取骨架。 - 对于曲率极大的区域,可改用邻域点拟合直线的方式计算切线方向,避免梯度法的误差。
- 过滤掉宽度异常值(比如远大于平均值的点)后再计算平均,结果更稳定。
内容的提问来源于stack exchange,提问作者F612
相关产品推荐
相关产品推荐

