This article introduces
The goal of this chapter is to detect the number of cracks and calculate the corresponding area of each crack.
Implementation ideas
Here we still need to write it in the form of a function. After all, we have a lot of two-dimensional data to detect. Here I named it detect_crack_areas to indicate the detection of the crack area, but at the same time it can return the number of cracks. The parameter can be an image img. In addition, I want to judge whether to draw a bounding box for the crack, so I need to give a bool value named Bbox, and then I want to give a distance threshold for crack merging. After all, the detection method is to see if the crack is connected. If it is only a pixel size, it seems unnecessary to add a crack number. In addition, although some are disconnected, they are indeed too small, and we don’t need to calculate them, so there is also a threshold for crack area filtering. This parameter is used to filter out crack areas whose area is smaller than the threshold.
Code
Here we go step by step to realize our above ideas.
The first step: convert the BGR image into a binary image, and perform a closed operation
The distance threshold for crack merging, here we give 3, this value is obtained from the test based on the picture of CrackForest. morphology.square(), morphology.rectangle() or morphology.disk() is a form of structural elements used in the closing operation, and you need to try it yourself. I use morphology.disk() here to perform better on my data.
binary_image = pz.BinaryImg(img)
connected_image = morphology.closing(binary_image, morphology.disk(3))
Step 2: Mark connected regions and obtain region attributes
The label function can be used to mark the connected area of the integer array, so we need to binarize the image. I don't want other colors to be mixed in. Connectivity refers to considering the connectivity between pixels. This can be explained in the API:
1-connectivity 2-connectivity diagonal connection close-up [ ] [ ] [ ] [ ] [ ] | \ | / | <- hop 2 [ ]--[x]--[ ] [ ]--[x]--[ ] [x]--[ ] | / | \ hop 1 [ ] [ ] [ ] [ ]
Obviously, what I want is an eight-connected region.
regionprops returns a list, each element of which represents a property of a connected region. Each element is an object that contains various attribute information related to the connected region, such as area, centroid coordinates, bounding rectangle, perimeter, etc.
labeled_image = measure.label(connected_image, connectivity=2)
region_props = measure.regionprops(labeled_image)
Step Three: Mark the Cracks
The markings here are easy to understand. Simply speaking, I need to know the information about the cracks every day, so I need to name them. Here I use capital letters to represent them. I think that under normal circumstances, the number of horizontal, vertical and oblique cracks is not too much. It is more than enough to name them with English letters.
area = {}
crack_label = ord('A')
Step 4: Filter the fracture area
for region in region_props:
area_value = region.area
if area_value >= 50:
Even if the cracks are really far apart, they are so small that we don't need to think of it as a crack anymore.
Step 5: whether to frame it
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)
After the area is filtered, we can use the Boolean value Bbox to determine whether to mark and dialog box.
full text code
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}")
Here we look at the marking of a picture, where our Bbox is set to False.
You can modify it like this:
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}")
Let me explain the situation of mark A. There is a small gap in the middle, but after the actual closing operation, the cracks are connected together. Although the crack C is separated, the area of the separated part is less than the set 50, so it is not included.