前段时间我将网上最大内切圆算法进行了代码的整理,原先博主上传的代码稍微有点乱,可能也是它自己使用,大家可以看这篇整理好的:最大内切圆算法计算裂缝宽度。
最大内切圆算法详解
一个圆与给定的多边形或曲线的每一条边或曲线都相切的圆。而我们就是需要计算的是给定图像的轮廓的最大内切圆,也就是与轮廓的每一条边都相切的圆中直径最大的圆。这样直径就是我们的轮廓的宽度了。
既然要求轮廓的内接圆,从圆的特点来说,想要唯一的确定一个圆,就是要知道它的圆心和半径。好的,那现在的问题就从求取轮廓的内接圆,巧妙地转变成求取某个点和一个多边形的距离和关系。
在opencv中有一个函数pointPolygonTest就是能够得到某个点和某个多边形之间的关系,例如这个点是在多边形内部、外部、或者是在多边形上,还能得到该点距离多边形的像素距离。那问题其实就很好解决了。我们再使用cv2.minMaxLoc(src)来获得给定的数组中寻找最小值和最大值的位置,它的语法如下
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(src)
其中,src是输入的数组或图像。函数会返回最小值min_val和最大值max_val,以及它们在数组中的位置最小值的位置min_loc和最大值的位置max_loc。
讲到这里我想大家也知道怎么求轮廓的宽度了吧,即为:max_loc * 2。
详细代码
import cv2
import string
import numpy as np
import pyzjr as pz
def incircle(img, contours_arr, color=(0, 0, 255)):
"""
轮廓最大内切圆算法,所有轮廓当中的内切圆
"""
result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
raw_dist = np.zeros(img.shape, dtype=np.float32)
letters = list(string.ascii_uppercase)
label = {}
for k, contours in enumerate(contours_arr):
for i in range(img.shape[0]):
for j in range(img.shape[1]):
raw_dist[i, j] = cv2.pointPolygonTest(contours, (j, i), True)
min_val, max_val, _, max_dist_pt = cv2.minMaxLoc(raw_dist)
label[letters[k]] = max_val * 2
radius = int(max_val)
cv2.circle(result, max_dist_pt, radius, color, 1, 1, 0)
return result, label
if __name__=="__main__":
path = r"D:\PythonProject\RoadCrack\dimension2_data\num/001.png"
img = cv2.imread(path)
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
thresh = pz.BinaryImg(img)
contours_arr, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
result, label = incircle(gray_img, contours_arr)
print("裂缝宽度:",label)
cv2.imwrite("result.png",result)
先检测轮廓,你可以使用opencv的findContours。需要确保是ndarray数组的形式。
裂缝宽度: {'A': 5.656854152679443, 'B': 4.4721360206604}
现在我们就能知道两条裂缝对应的最大内切圆直径,即裂缝的宽度。
算法对比
并且从时间角度来看:
- 原先的最大内切圆算法: 1.79125 sec
- 改进后的内切圆算法: 1.05487 sec
从计算的直径上来看:
- 原先的最大内切圆算法:13.81
- 改进后的内切圆算法:{'A': 14.0}
这里的实现比较简单,只是嵌套的循环比较多,但能存储每条裂缝对应的宽度。