解析以下基于opencv的裂缝宽度检测算法(计算轮廓最大内切圆算法)的程序 import cv2 import math import random import numpy as np from numpy.ma import cos, sin import matplotlib.pyplot as plt def max_circle(img_path): ''' 计算轮廓内切圆算法 Args: img_path: 输入图片路径,图片需为二值化图像 Returns: ''' img = cv2.imread(img_path, cv2.IMREAD_COLOR) # 灰度处理 img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 图片二值化,缺少这一步也可以,但是图像的二值化可以使图像中数据量大为减少,从而能凸显出目标的轮廓,减小计算量 ret, thresh = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 寻找二值图像的轮廓 contous, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) """ 第二个参数表示轮廓的检索模式,有四种: cv2.RETR_EXTERNAL表示只检测外轮廓 cv2.RETR_LIST检测的轮廓不建立等级关系 cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。 cv2.RETR_TREE建立一个等级树结构的轮廓。 第三个参数method为轮廓的近似办法 cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1 cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息 cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法 """ expansion_circle_list = [] # 所有裂缝最大内切圆半径和圆心列表 # 可能一张图片中存在多条裂缝,对每一条裂缝进行循环计算 for c in contous: # 定义能包含此裂缝的最小矩形,矩形为水平方向 left_x = min(c[:, 0, 0]) right_x = max(c[:, 0, 0]) down_y = max(c[:, 0, 1]) up_y = min(c[:, 0, 1]) # 最小矩形中最小的边除2,裂缝内切圆的半径最大不超过此距离 upper_r = min(right_x - left_x, down_y - up_y) / 2 # 定义相切二分精度precision precision = math.sqrt((right_x - left_x) ** 2 + (down_y - up_y) ** 2) / (2 ** 13) # 构造包含轮廓的矩形的所有像素点 Nx = 2 ** 8 Ny = 2 ** 8 pixel_X = np.linspace(left_x, right_x, Nx) pixel_Y = np.linspace(up_y, down_y, Ny) # 从坐标向量中生成网格点坐标矩阵 xx, yy = np.meshgrid(pixel_X, pixel_Y) # 筛选出轮廓内所有像素点 in_list = [] for i in range(pixel_X.shape[0]): for j in range(pixel_X.shape[0]): # cv2.pointPolygonTest可查找图像中的点与轮廓之间的最短距离.当点在轮廓外时返回负值,当点在内部时返回正值,如果点在轮廓上则返回零 # 统计裂缝内的所有点的坐标 if cv2.pointPolygonTest(c, (xx[i][j], yy[i][j]), False) > 0: in_list.append((xx[i][j], yy[i][j])) in_point = np.array(in_list) # 随机搜索百分之一的像素点提高内切圆半径下限 N = len(in_point) rand_index = random.sample(range(N), N // 100) rand_index.sort() radius = 0 big_r = upper_r # 裂缝内切圆的半径最大不超过此距离 center = None for id in rand_index: tr = iterated_optimal_incircle_radius_get(c, in_point[id][0], in_point[id][1], radius, big_r, precision) if tr > radius: radius = tr center = (in_point[id][0], in_point[id][1]) # 只有半径变大才允许位置变更,否则保持之前位置不变 # 循环搜索剩余像素对应内切圆半径 loops_index = [i for i in range(N) if i not in rand_index] for id in loops_index: tr = iterated_optimal_incircle_radius_get(c, in_point[id][0], in_point[id][1], radius, big_r, precision) if tr > radius: radius = tr center = (in_point[id][0], in_point[id][1]) # 只有半径变大才允许位置变更,否则保持之前位置不变 expansion_circle_list.append([radius, center]) # 保存每条裂缝最大内切圆的半径和圆心 # 输出裂缝的最大宽度 print('裂缝宽度:', round(radius * 2, 2)) print('---------------') expansion_circle_radius_list = [i[0] for i in expansion_circle_list] # 每条裂缝最大内切圆半径列表 max_radius = max(expansion_circle_radius_list) max_center = expansion_circle_list[expansion_circle_radius_list.index(max_radius)][1] print('最大宽度:', round(max_radius * 2, 2)) # 绘制轮廓 cv2.drawContours(img_original, contous, -1, (0, 0, 255), -1) # 绘制裂缝轮廓最大内切圆 for expansion_circle in expansion_circle_list: radius_s = expansion_circle[0] center_s = expansion_circle[1] if radius_s == max_radius: # 最大内切圆,用蓝色标注 cv2.circle(img_original, (int(max_center[0]), int(max_center[1])), int(max_radius), (255, 0, 0), 2) else: # 其他内切圆,用青色标注 cv2.circle(img_original, (int(center_s[0]), int(center_s[1])), int(radius_s), (255, 245, 0), 2) cv2.imshow('Inscribed_circle', img_original) cv2.imwrite('inference/output/Inscribed_circle.png', img_original) cv2.waitKey(0) cv2.destroyAllWindows() # # matplotlib效果测试 # plot_x = np.linspace(0, 2 * math.pi, 100) # circle_X = max_center[0] + max_radius * cos(plot_x) # circle_Y = max_center[1] + max_radius * sin(plot_x) # plt.figure() # plt.imshow(img_gray) # plt.plot(circle_X, circle_Y) # plt.show() def iterated_optimal_incircle_radius_get(contous, pixelx, pixely, small_r, big_r, precision): ''' 计算轮廓内最大内切圆的半径 Args: contous: 轮廓像素点array数组 pixelx: 圆心x像素坐标 pixely: 圆心y像素坐标 small_r: 之前所有计算所求得的内切圆的最大半径,作为下次计算时的最小半径输入,只有半径变大时才允许位置变更,否则保持之前位置不变 big_r: 圆的半径最大不超过此距离 precision: 相切二分精度,采用二分法寻找最大半径 Returns: 轮廓内切圆的半径 ''' radius = small_r L = np.linspace(0, 2 * math.pi, 360) # 确定圆散点剖分数360, 720 circle_X = pixelx + radius * cos(L) circle_Y = pixely + radius * sin(L) for i in range(len(circle_Y)): if cv2.pointPolygonTest(contous, (circle_X[i], circle_Y[i]), False) < 0: # 如果圆散集有在轮廓之外的点 return 0 while big_r - small_r >= precision: # 二分法寻找最大半径 half_r = (small_r + big_r) / 2 circle_X = pixelx + half_r * cos(L) circle_Y = pixely + half_r * sin(L) if_out = False for i in range(len(circle_Y)): if cv2.pointPolygonTest(contous, (circle_X[i], circle_Y[i]), False) < 0: # 如果圆散集有在轮廓之外的点 big_r = half_r if_out = True if not if_out: small_r = half_r radius = small_r return radius if __name__ == '__main__': img_original_path = 'inference/images/crack_test1.png' img_gray = 'inference/output/test_crack.png' img_original = cv2.imread('inference/images/crack_test1.png') max_circle(img_gray)
时间: 2025-07-23 21:04:46 浏览: 3
<think>我们被要求解析一个基于OpenCV的裂缝宽度检测算法(计算轮廓最大内切圆算法)的程序。
程序的主要功能是:对于输入的二值化图像(裂缝为白色,背景为黑色),检测裂缝轮廓,并计算每个轮廓的最大内切圆,从而得到裂缝的宽度(即最大内切圆的直径)。
程序步骤解析如下:
1. 读取图像,并转换为灰度图像(虽然输入要求是二值化图像,但这里还是做了灰度转换,可能是为了通用性)。
2. 对灰度图像进行二值化(使用OTSU方法,并且采用THRESH_BINARY_INV模式,即反转二值化,这样裂缝区域变为白色(255),背景为黑色(0))。
3. 使用cv2.findContours寻找二值图像中的轮廓。这里使用了RETR_TREE(检索所有轮廓并建立等级树)和CHAIN_APPROX_SIMPLE(压缩轮廓点)模式。
4. 对于每个轮廓,进行以下处理:
a. 计算包含轮廓的最小外接矩形(水平方向),并得到该矩形的四个边界(left_x, right_x, up_y, down_y)。
b. 计算该矩形的较小边的一半作为内切圆半径的上限(upper_r)。
c. 设置二分精度(precision),基于矩形的对角线长度除以2^13。
d. 在最小外接矩形区域内生成网格点(Nx x Ny 个点,这里Nx=2^8, Ny=2^8)。
e. 遍历网格点,使用cv2.pointPolygonTest判断点是否在轮廓内,将轮廓内的点收集到in_list中。
f. 从轮廓内的点中随机选取1%的点作为初始点,使用iterated_optimal_incircle_radius_get函数计算以这些点为圆心的最大内切圆半径(初始半径下限为0,上限为upper_r)。
g. 在随机点中迭代更新最大半径和圆心(只有半径变大才更新圆心)。
h. 遍历剩余99%的点,同样更新最大半径和圆心。
i. 将当前轮廓的最大内切圆半径和圆心保存到expansion_circle_list中,并输出该裂缝的宽度(半径的两倍)。
5. 从所有轮廓的最大内切圆中找出最大半径,即最宽的裂缝宽度。
6. 在原图上绘制所有轮廓(红色填充),并绘制每个轮廓的最大内切圆(最大内切圆用蓝色,其他用青色)。
7. 显示并保存结果图像。
辅助函数:iterated_optimal_incircle_radius_get
功能:对于给定的圆心(pixelx, pixely),在轮廓contous内,通过二分法寻找最大内切圆半径(在[small_r, big_r]区间内)。
步骤:
a. 首先用当前small_r(初始为0)作为半径,生成360个圆上的点,检查这些点是否都在轮廓内(如果有一个点不在,则返回0,因为初始半径为0,所以第一次检查总是通过?但注意初始small_r可能是0,而0不会在轮廓外,所以第一次检查会通过)。
b. 使用二分法,不断将半径设置为区间中点(half_r),然后检查以half_r为半径的圆上的点是否都在轮廓内:
- 如果有一个点不在轮廓内,则说明半径太大,将big_r设为half_r。
- 如果所有点都在轮廓内,则说明半径可以更大,将small_r设为half_r。
c. 当二分区间长度小于precision时停止,返回small_r作为最大内切圆半径。
注意:在max_circle函数中,我们注意到它读取的是彩色图像,但后续处理又转灰度并二值化。然而,函数注释要求输入是二值化图像,这似乎有些矛盾。实际上,程序内部进行了二值化,所以输入可以是灰度或彩色,但最终都会被二值化处理。
另外,程序最后调用max_circle时传入的是img_gray(一个灰度图像的路径),但代码中img_original_path和img_original(原图)被用于绘制结果。注意,在max_circle函数内部,读取的是img_path(即传入的灰度图像路径),而绘制轮廓时使用的是外部变量img_original(这个变量是在主函数中读取的彩色原图)。这里存在一个潜在的问题:如果传入的灰度图像和主函数中读取的彩色原图不是同一张,则绘制会出错。但在这个例子中,传入的是灰度图像路径,而主函数中读取的彩色原图路径是固定的('inference/images/crack_test1.png'),而传入的灰度图像路径是'inference/output/test_crack.png'。这可能是两个不同的图像。因此,在绘制轮廓时,实际上是在彩色原图上绘制,而处理的是灰度图像,这可能导致不一致。建议在max_circle函数内部读取原图,或者确保传入的路径是同一张图片。
程序中的一些细节:
- 在寻找轮廓内点时,使用了网格点,然后逐个判断是否在轮廓内。对于大图像,这可能会比较慢(网格点有256*256=65536个)。但实际裂缝区域可能很小,所以网格点数量可以接受。
- 随机选取1%的点先进行迭代,然后再迭代剩余点,这样可能更快地找到较大的内切圆,从而在后续迭代中减少计算(因为small_r的下限提高了)。
现在,根据要求,在回答末尾提出3-5个相关问题。
§§相关问题§§
1. 在max_circle函数中,为什么在二值化时使用了THRESH_BINARY_INV模式?这对裂缝检测有什么影响?
2. 程序在计算轮廓内切圆时采用了随机采样1%的像素点先进行迭代,然后再迭代剩余点,这样做的优势是什么?有没有可能漏掉真正的最大内切圆?
3. 在iterated_optimal_incircle_radius_get函数中,二分法寻找最大半径时,初始small_r参数传入的是之前计算的最大半径,这样做有什么好处?
4. 程序在绘制结果时使用了外部变量img_original(主函数中读取的原图),而处理的是传入的灰度图像,这可能导致不一致。如何修改代码以确保绘制结果的正确性?
5. 在网格点生成时,使用了固定的Nx=2^8和Ny=2^8,即256个点。如果裂缝区域非常大,这样的网格点密度是否足够?如何根据裂缝区域大小动态调整网格点数量?</think>### 基于OpenCV的裂缝宽度检测算法解析
该程序通过计算裂缝轮廓的最大内切圆直径来测量裂缝宽度,核心算法流程如下:
1. **图像预处理**
- 读取彩色图像并转为灰度图
- 使用OTSU自适应阈值进行二值化(`THRESH_BINARY_INV`将裂缝转为白色前景)
- 通过`findContours`提取裂缝轮廓(使用`RETR_TREE`模式保留层级关系)
2. **轮廓处理流程(每条裂缝独立处理)**
```python
for c in contous:
# 1. 计算轮廓的最小外接矩形
left_x = min(c[:, 0, 0])
right_x = max(c[:, 0, 0])
...
# 2. 确定半径搜索范围
upper_r = min(矩形宽, 矩形高)/2 # 理论最大半径
precision = 对角线长度/2^13 # 二分法精度
# 3. 生成网格点并筛选轮廓内部点
xx, yy = np.meshgrid() # 256x256网格
in_point = [] # 存储轮廓内部点
for point in 网格点:
if pointPolygonTest() > 0: # 点在轮廓内
in_point.append(point)
# 4. 两阶段搜索最大内切圆
# 阶段1:随机采样1%的点快速确定半径下限
# 阶段2:遍历剩余99%点优化半径
for 点 in 采样点集:
tr = iterated_optimal_incircle_radius_get(...)
if tr > current_radius: # 发现更大半径
radius = tr
center = 当前点坐标
```
3. **核心函数:`iterated_optimal_incircle_radius_get`**
```python
def iterated_optimal_incircle_radius_get(...):
# 1. 验证当前半径有效性(360度圆周点检测)
circle_X = pixelx + radius * cos(L) # L=0~2π
for 点 in 圆周点:
if pointPolygonTest() < 0: # 点在轮廓外
return 0
# 2. 二分法搜索最大半径
while big_r - small_r >= precision:
half_r = (small_r+big_r)/2
生成新圆周点
if 有圆周点在轮廓外:
big_r = half_r # 收缩上限
else:
small_r = half_r # 提升下限
return small_r
```
4. **结果输出与可视化**
- 输出每条裂缝宽度(最大内切圆直径)
- 在原图上绘制:红色轮廓/青色内切圆/最大内切圆(蓝色)
- 保存结果图像并显示
### 算法特点
1. **高效搜索策略**:通过1%随机采样快速确定半径下限,减少99%点的计算量
2. **精确二分定位**:基于几何特征的自适应精度控制(`precision`)
3. **几何约束优化**:利用外接矩形确定半径上限(`upper_r`)
4. **鲁棒性验证**:360点圆周采样确保内切圆完全在轮廓内
### 注意事项
1. 输入需为**二值化图像**(裂缝=白色)
2. 主程序中的`img_original`变量需与处理图像一致(否则绘制错位)
3. 网格点密度(256×256)影响计算精度与速度
阅读全文