在进行构件破损检测时,我们通过语义分割得到很多各裂缝的实例。那么,如何判断每个裂缝是属于横向裂缝、纵向裂缝、斜向裂缝还是网状裂缝呢?本文提供了一个解决方案。
主要利用分割得到的mask的骨架线,对其进行X、Y两个方向的投影,得到两个投影直方图,然后根据投影情况进行判断,即可判断其类型;代码如下:
"""
传统方法进行裂缝分类
"""
import numpy as np
def parse_minAreaRect(rect):
"""
旋转矩形框长边与x轴的夹角,在第一象限时,角度为-90°~0°,在第二象限时,角度为0°~90°;
"""
w, h = rect[1]
if w > h:
angle = int(rect[2])
else:
angle = -(90 - int(rect[2]))
return w, h, angle
class CrackClassifier(object):
def __init__(self, Thres_num=3, Thres_hist_ratio=0.5, Thres_hw_ratio=10):
self.classes = {0: 'horizontal', 1: 'vertical', 2: 'oblique', 3: 'mesh'}
self.Thres_num = Thres_num
self.Thres_hist_ratio = Thres_hist_ratio
self.Thres_hw_ratio = Thres_hw_ratio
def classify(self, minAreaRect, skeleton_pts, HW):
""" 针对当前crack instance,对其进行分类;主要利用了骨骼点双向投影直方图、旋转矩形框宽高比/角度;
minAreaRect: 最小外接矩形框,[(cx, cy), (w, h), angle];
skeleton_pts: 骨骼点坐标;
HW: 当前patch的高、宽;
"""
H, W = HW
w, h, angle = parse_minAreaRect(minAreaRect)
if w / h < self.Thres_hw_ratio or h / w < self.Thres_hw_ratio:
pts_y, pts_x = skeleton_pts[:, 0], skeleton_pts[:, 1]
hist_x = np.histogram(pts_x, W)
hist_y = np.histogram(pts_y, H)
if self.hist_judge(hist_x[0]) and self.hist_judge(hist_y[0]):
return 3
return self.angle2cls(angle)
def hist_judge(self, hist_v):
less_than_T = list((hist_v > 0) & (hist_v <= self.Thres_num)).count(True)
more_than_T = list(hist_v > self.Thres_num).count(True)
return more_than_T / (less_than_T + 1e-5) > self.Thres_hist_ratio
@staticmethod
def angle2cls(angle):
angle = abs(angle)
assert 0 <= angle <= 90, "ERROR: angle should be in 0~90!"
if angle < 35:
return 0
elif 35 <= angle <= 55:
return 2
elif angle > 55:
return 1
else:
return None
将上面的代码保存为一个文件:crack_classify.py;然后,再写一个测试代码,利用几张图进行测试。
用到的图片如下(保存到一个文件夹中):
测试代码如下:
import os
import time
import matplotlib.pyplot as plt
import numpy as np
import cv2
from crack_classify import CrackClassifier
def get_skeleton(blobs):
"""
骨骼点提取
"""
skeleton = skeletonize(blobs) # ndarray, 为TRUE的元素代表骨骼线的位置
skeleton_pts = np.argwhere(skeleton)
return skeleton, skeleton_pts
def parse_minAreaRect(rect):
"""
旋转矩形框长边与x轴的夹角,在第一象限时,角度为-90°~0°,在第二象限时,角度为0°~90°;
"""
w, h = rect[1]
if w > h:
angle = int(rect[2])
else:
angle = -(90 - int(rect[2]))
return w, h, angle
if __name__ == '__main__':
masks_dir = "path_to_imgs" # 这里改为存放上面图片的路径
results_save_dir = "results"
os.makedirs(results_save_dir, exist_ok=True)
classifier = CrackClassifier()
for mask_file in os.listdir(masks_dir):
mask_path = os.path.join(masks_dir, mask_file)
mask = cv2.imread(mask_path)
H, W, C = mask.shape
mask_copy = mask.copy()
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
blob = mask.copy()
blob[blob > 0] = 1
# 获取裂缝骨架坐标点
skeleton, skeleton_pts = get_skeleton(blob)
# 获取裂缝的最小外接矩形框的宽、高、倾斜角
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contour_merge = np.vstack(contours)
minAreaRect = cv2.minAreaRect(contour_merge)
t0 = time.time()
w, h, angle = parse_minAreaRect(minAreaRect)
print(w, h, angle)
# 根据mask外接矩形框以及骨架线判断裂缝类型
pts_y, pts_x = skeleton_pts[:, 0], skeleton_pts[:, 1]
hist_x = np.histogram(pts_x, W)
hist_y = np.histogram(pts_y, H)
print("time cost: ", time.time()-t0)
result = classifier.classify(minAreaRect, skeleton_pts, HW=(H, W))
crack_type = classifier.classes[result]
print(crack_type)
# plot
T = classifier.Thres_num
plt.figure()
plt.subplot(222)
plt.plot(hist_x[1][:-1], [T]*len(hist_x[0]), 'r')
plt.bar(hist_x[1][:-1], hist_x[0])
plt.subplot(224)
plt.plot(hist_y[1][:-1], [T] * len(hist_y[0]), 'r')
plt.bar(hist_y[1][:-1], hist_y[0])
plt.subplot(121)
plt.imshow(mask_copy)
plt.title("crack type: {}".format(crack_type))
plt.savefig(os.path.join(results_save_dir, mask_file))
plt.show()
运行上述代码,即可得到下图的效果: