如何用Python/OpenCV从地籍图提取单条中心边界(解决Canny双边界问题)
问题描述
我尝试使用Canny边缘检测提取地籍图的边界,但检测出两条外部线条,我需要得到单条中心线条,请问如何用Python/OpenCV实现?
原始地籍图:
Canny检测得到的双线结果:
已尝试的代码:
import numpy as np import matplotlib.pyplot as plt import cv2 img_gray = cv2.imread('ainta.tiff') sigma = 2 threshold1=30 threshold2=60 img_blur = cv2.GaussianBlur(img_gray,(5,5),sigmaX=sigma,sigmaY=sigma) res = cv2.Canny(img_blur,threshold1=threshold1,threshold2=threshold2) cv2.imshow("Canny", res) cv2.waitKey(0)
解决方案
Canny检测得到的双线是目标边界的两侧边缘,要得到单条中心线,可通过骨架提取(细化)或距离变换实现,以下是几种实用方案:
方案1:自定义形态学细化算法
通过迭代腐蚀与差分操作,逐步剥离线条外层,保留中心骨架:
import numpy as np import cv2 # 以灰度模式读取图像(原代码未指定模式,会导致BGR转灰度的潜在问题) img_gray = cv2.imread('ainta.tiff', cv2.IMREAD_GRAYSCALE) # 原有Canny检测流程 sigma = 2 threshold1 = 30 threshold2 = 60 img_blur = cv2.GaussianBlur(img_gray, (5,5), sigmaX=sigma, sigmaY=sigma) canny_edges = cv2.Canny(img_blur, threshold1=threshold1, threshold2=threshold2) # 自定义骨架提取函数 def skeletonize(img): size = np.size(img) skel = np.zeros(img.shape, np.uint8) # 使用十字形结构元素 element = cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3)) done = False while not done: eroded = cv2.erode(img, element) temp = cv2.dilate(eroded, element) # 提取当前迭代的边缘层 temp = cv2.subtract(img, temp) skel = cv2.bitwise_or(skel, temp) img = eroded.copy() # 当图像全黑时停止迭代 zeros = size - cv2.countNonZero(img) if zeros == size: done = True return skel # 生成单条中心线 center_line = skeletonize(canny_edges) # 显示结果 cv2.imshow("Canny Edges", canny_edges) cv2.imshow("Center Line", center_line) cv2.waitKey(0) cv2.destroyAllWindows()
方案2:使用OpenCV ximgproc快速细化(推荐)
OpenCV的扩展模块ximgproc提供了现成的细化算法,效率更高,支持两种经典算法:
import cv2 from cv2 import ximgproc # 读取灰度图 img_gray = cv2.imread('ainta.tiff', cv2.IMREAD_GRAYSCALE) # Canny检测 sigma = 2 threshold1 = 30 threshold2 = 60 img_blur = cv2.GaussianBlur(img_gray, (5,5), sigmaX=sigma, sigmaY=sigma) canny_edges = cv2.Canny(img_blur, threshold1=threshold1, threshold2=threshold2) # 应用Zhang-Suen细化算法(也可替换为THINNING_GUOHALL) center_line = ximgproc.thinning(canny_edges, thinningType=ximgproc.THINNING_ZHANGSUEN) cv2.imshow("Center Line", center_line) cv2.waitKey(0) cv2.destroyAllWindows()
注意:若提示找不到
ximgproc,需安装带contrib的OpenCV:pip install opencv-contrib-python
方案3:距离变换提取中心线
通过距离变换计算线条内每个点到边缘的距离,取距离峰值作为中心线:
import cv2 import numpy as np img_gray = cv2.imread('ainta.tiff', cv2.IMREAD_GRAYSCALE) # Canny检测 sigma = 2 threshold1 = 30 threshold2 = 60 img_blur = cv2.GaussianBlur(img_gray, (5,5), sigmaX=sigma, sigmaY=sigma) canny_edges = cv2.Canny(img_blur, threshold1=threshold1, threshold2=threshold2) # 距离变换(L2距离,5x5核) dist_transform = cv2.distanceTransform(canny_edges, cv2.DIST_L2, 5) # 取距离最大值的50%作为阈值,提取中心线 _, center_line = cv2.threshold(dist_transform, 0.5 * dist_transform.max(), 255, 0) center_line = center_line.astype(np.uint8) cv2.imshow("Center Line", center_line) cv2.waitKey(0) cv2.destroyAllWindows()
注意事项
- 读取图像时务必指定
cv2.IMREAD_GRAYSCALE,原代码未指定模式会以BGR格式读取,可能干扰Canny检测效果; - 若Canny结果存在线条断裂,可先对边缘图做一次形态学膨胀(
cv2.dilate),填补小缺口后再细化; - 不同细化算法适配不同场景,可根据实际地籍图的线条粗细、复杂度选择最优方案。
内容的提问来源于stack exchange,提问作者alam siam
相关产品推荐
相关产品推荐

