如何从二值图像提取裂纹轮廓?Python代码遇维度不匹配错误
解决二值图像裂纹轮廓提取中的形状不匹配问题
问题场景
要从二值图像中提取裂纹轮廓并可视化宽度分布,但运行代码时出现形状不匹配的报错,无法完成计算。
原代码:
import matplotlib.pyplot as plt import matplotlib matplotlib.rc("font", family='SimHei') import numpy as np import cv2 from matplotlib.colors import ListedColormap im = cv2.imread(r'Desktop\\file\\lunwen\\test\\image1\\1381_8.jpg', cv2.IMREAD_GRAYSCALE) im = cv2.threshold(im, 128, 1, cv2.THRESH_BINARY)[1] result = np.zeros_like(im, dtype='uint32') diff = np.diff(im.astype(np.int8), axis=1) y, x_min = np.nonzero(diff == 1) _, x_max = np.nonzero(diff == -1) width_list = x_max - x_min print('max_width:', np.max(width_list)) print('min_width:', np.min(width_list)) print('mean_width:', np.mean(width_list)) for i in range(x_min.size): result[y[i], x_min[i]:x_max[i]] = width_list[i] viridis = plt.cm.viridis newcolors = viridis(np.linspace(0, 1, 256)) newcolors[0, :] = np.array([1, 1, 1, 1]) newcmp = ListedColormap(newcolors) plt.figure(figsize=(8, 6)) plt.title('Crack Contour') cax = plt.imshow(result, cmap=newcmp) plt.axis('off') cbar = plt.colorbar(cax, fraction=0.03, pad=0.06,label='Width', orientation='vertical') plt.savefig('result_with_colorbar.png', bbox_inches='tight', pad_inches=0.1) plt.show()
报错信息:
Traceback (most recent call last): File "E:\\Test.py", line 30, in <module> width_list = x_max - x_min ValueError: operands could not be broadcast together with shapes (797,) (800,)
问题根源
直接通过np.nonzero提取的左右边界点,无法保证每行的左边界(diff==1)和右边界(diff==-1)数量完全对应。比如图像首列是裂纹时,没有左边界;末列是裂纹时,没有右边界;或者裂纹区域有断点,都会导致两边数量不一致。
修复代码
按行分组处理边界点,确保每个左边界都能找到对应的右边界:
import matplotlib.pyplot as plt import matplotlib matplotlib.rc("font", family='SimHei') import numpy as np import cv2 from matplotlib.colors import ListedColormap # 读取并二值化图像 im = cv2.imread(r'Desktop\\file\\lunwen\\test\\image1\\1381_8.jpg', cv2.IMREAD_GRAYSCALE) im = cv2.threshold(im, 128, 1, cv2.THRESH_BINARY)[1] result = np.zeros_like(im, dtype='uint32') height, width = im.shape # 存储所有宽度数据 width_list = [] # 按行遍历处理边界 for y in range(height): row = im[y] # 找到当前行的所有前景起始和结束位置 starts = [] ends = [] # 处理行首就是前景的情况 if row[0] == 1: starts.append(0) # 检测行内的边界变化 diff = np.diff(row.astype(np.int8)) x_starts = np.where(diff == 1)[0] + 1 # diff=1表示从0变1,起始位置是当前索引+1 x_ends = np.where(diff == -1)[0] # diff=-1表示从1变0,结束位置是当前索引 starts.extend(x_starts.tolist()) ends.extend(x_ends.tolist()) # 处理行末是前景的情况 if row[-1] == 1: ends.append(width - 1) # 配对起始和结束点,计算宽度 for s, e in zip(starts, ends): crack_width = e - s + 1 width_list.append(crack_width) result[y, s:e+1] = crack_width # 输出宽度统计信息 if width_list: print('max_width:', np.max(width_list)) print('min_width:', np.min(width_list)) print('mean_width:', np.mean(width_list)) else: print('未检测到裂纹区域') # 可视化结果 viridis = plt.cm.viridis newcolors = viridis(np.linspace(0, 1, 256)) newcolors[0, :] = np.array([1, 1, 1, 1]) newcmp = ListedColormap(newcolors) plt.figure(figsize=(8, 6)) plt.title('Crack Contour') cax = plt.imshow(result, cmap=newcmp) plt.axis('off') cbar = plt.colorbar(cax, fraction=0.03, pad=0.06, label='Width', orientation='vertical') plt.savefig('result_with_colorbar.png', bbox_inches='tight', pad_inches=0.1) plt.show()
关键修改说明
- 改为按行遍历,针对每行单独处理边界点,避免跨行的边界点配对错误
- 主动处理行首/行末为前景的特殊情况,补充缺失的边界点
- 对每行的起始、结束点一一配对,确保数量一致,解决形状不匹配问题
- 宽度计算修正为
e - s + 1,符合像素数量的统计逻辑
内容的提问来源于stack exchange,提问作者Abukeram
相关产品推荐
相关产品推荐

