本章节的目标是检测裂缝的个数,并计算出每条裂缝相应的面积。
这里还是要写成函数的形式,毕竟我们要检测的二维数据有很多,这里我命名detect_crack_areas表示检测裂缝面积,但同时又能返回裂缝的数量。参数可以给一个图片img,除此外,我想要判断是否要对裂缝画出一个边界框,所以要给一个bool值名为Bbox,然后我想要再给一个裂缝合并的距离阈值,毕竟检测的方式是看裂缝是否连通,如果只不过是断开了一个像素点大小,却要再添加一个裂缝个数似乎也没有这个必要。另外有些虽然断开了,但是确实是太小了,咱们也没必要去计算,所以还要有一个裂缝面积过滤的阈值,这个参数用于过滤掉面积小于阈值的裂缝区域。
这里我们一步一步的去实现我们上面的思路。
裂缝合并的距离阈值,这里我们给了3,这个值为我根据CrackForest的图片进行测试得到的。morphology.square()、morphology.rectangle() 或者 morphology.disk()为闭运算中使用的结构元素的一种形式,也是需要大家自己去尝试的,我这里采用了morphology.disk()是在我的数据上表现的比较好。
binary_image = pz.BinaryImg(img)
connected_image = morphology.closing(binary_image, morphology.disk(3))
label这个函数可以用于标记整数数组的连接区域,所以我们需要对图片进行二值化,我并不希望有其他的颜色混入,connectivity指考虑像素之间的连接性,这个可以看API里面是怎么解释的:
1-connectivity 2-connectivity diagonal connection close-up [ ] [ ] [ ] [ ] [ ] | \ | / | <- hop 2 [ ]--[x]--[ ] [ ]--[x]--[ ] [x]--[ ] | / | \ hop 1 [ ] [ ] [ ] [ ]
很显然,我希望的是八连通的区域。
regionprops返回的是一个列表,其中的每个元素表示一个连通区域的属性。每个元素是一个对象,包含了与该连通区域相关的各种属性信息,例如面积、质心坐标、外接矩形框、周长等。
labeled_image = measure.label(connected_image, connectivity=2)
region_props = measure.regionprops(labeled_image)
这里的标记很好理解,简单来说就是我需要知道每天裂缝的信息,那么就需要给它命名,这里我采用了大写的英文字母来表示,我认为一般情况下,横向、纵向、斜裂缝的个数都不会太多,用英文字母来进行命名完全绰绰有余。
area = {}
crack_label = ord('A')
for region in region_props:
area_value = region.area
if area_value >= 50:
即使裂缝确实是间隔的比较的远,但却又太小了,这样我们也没有必要再认为它是一条裂缝了。
if Bbox:
minr, minc, maxr, maxc = region.bbox
pz.Boxcenter_text(img, [minc, minr, maxc, maxr], Z.green, chr(crack_label), Z.red, 0.7, 2)
在经过面积过滤后,我们就可以通过布尔值Bbox来判断是否要进行标记和话框了。
import cv2
import pyzjr as pz
import pyzjr.Z as Z
from skimage import morphology
from skimage import measure
def detect_crack_areas(img, Bbox=True, merge_threshold=3, area_threshold=50):
"""
检测裂缝区域并计算裂缝面积。
morphology.square()、morphology.rectangle() 或者 morphology.disk()
:param img: 输入图像。
:param Bbox: 是否绘制边界框,默认为 True。
:param merge_threshold: 裂缝合并的距离阈值,默认为 3。
:param area_threshold: 裂缝面积过滤的阈值,默认为 50。
:return: 裂缝面积的字典和裂缝数量。
"""
binary_image = pz.BinaryImg(img)
connected_image = morphology.closing(binary_image, morphology.disk(merge_threshold))
labeled_image = measure.label(connected_image, connectivity=2)
region_props = measure.regionprops(labeled_image)
area = {}
crack_label = ord('A')
for region in region_props:
area_value = region.area
if area_value >= area_threshold:
if Bbox:
minr, minc, maxr, maxc = region.bbox
pz.Boxcenter_text(img, [minc, minr, maxc, maxr], Z.green, chr(crack_label), Z.red, 0.7, 2)
if crack_label <= ord('Z'):
area[chr(crack_label)] = area_value
crack_label += 1
if Bbox:
cv2.imshow("Image with Bounding Boxes", img)
cv2.waitKey(0)
CrackNum = len(area)
return area, CrackNum
if __name__=="__main__":
file_path = r"E:\pythonconda2\dimension2_data\num"
img_paths = pz.getPhotopath(file_path)
for img_path in img_paths:
img = cv2.imread(img_path)
area, CrackNum = detect_crack_areas(img, False)
print(f"图片{img_path[-7:]}裂缝个数为{CrackNum},裂缝的面积为:{area}")
这里我们来查看一张图片的标记情况,在上面我们的Bbox是设置为False。
你可以这样修改:
if __name__=="__main__":
img_path = r"E:\pythonconda2\dimension2_data\num\046.png"
img = cv2.imread(img_path)
area, CrackNum = detect_crack_areas(img)
print(f"裂缝个数为{CrackNum},裂缝的面积为:{area}")
我来说明一下标记A的情况,中间有一点小间隔,但实际经过闭运算后,这条裂缝是连接在一起的,而C裂缝虽然是分开的,但分开部分的面积小于了设定的50,所以也没有算进去。