图像处理-007形态变换(二)
Hit-and-Miss Transform(击中击不中)
Hit-and-Miss从二值图像中查找前景色/背景色的特定模式非常有用,与腐蚀,膨胀使用的kernel仅存在0,1不同,hit-and-Misss的kernel中除0,1外还存在空白。腐蚀,膨胀,开操作,闭操作,形态学梯度,顶帽,黑帽运算使用的kernel中1用来表示前景色,0表示不关注, 在hit-and-miss运算中的1表示前景色,0表示背景色,空白表示不关注。
以图006-14中展示的kernel为例,该kernel可用来找到二值图像中的角点,运算时将kernel置于图像之上,锚点依次与图像中的坐标P对应,P的邻域元素依次与kernel的邻域元素比较,除去kernel中的空白元素外,其余元素完全匹配时则P标记为1,否则为0.
图007-1 查找图像角点的kernel
使用图007-2中的kernel找出图像中的四个角点如图007-3所示
图007-2 寻找图像四角点的kernel
图007-3 二值图像Hit-And-Miss运算结果图
实现代码: C++
/**
* hit and miss
* @param img
* @return
*/
int Morphological::hit_and_miss_transform(const Mat &img) {
logger_info("======hit_and_miss_transform=====================");
// kernel size为7*7
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(7, 7));
Mat dst;
morphologyEx(img, dst, MORPH_HITMISS, kernel);
imshow("hit_and_miss", dst);
return EXIT_SUCCESS;
}
实现代码:python
# hit and miss
def hit_and_miss_transform(origin_img):
# 非灰度图像转化维灰度图像
if origin_img.shape[2] == 3:
gray_image = cv.cvtColor(origin_img, cv.COLOR_BGR2GRAY)
else:
gray_image = origin_img
titles = ["origin_image"]
images = [cv.cvtColor(gray_image, cv.COLOR_BGR2RGB)]
for i in range(3, 13, 2):
kernel = cv.getStructuringElement(cv.MORPH_RECT, (i, i))
dst = cv.morphologyEx(gray_image, cv.MORPH_HITMISS, kernel)
titles.append("hit_and_miss_kernel_" + str(i))
images.append(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
for i in range(6):
plt.subplot(2, 3, i + 1)
plt.imshow(images[i], 'gray', vmin=0, vmax=255)
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
图007-4 二值图像不同kernel大小下hit-and-miss效果图
Thining(细化)
细化会删除二值图像中选定的前景(白色)像素,类似于腐蚀、开操作,删除不必要的像素点,仅保留图像的轮廓。
dst = t h i n i n g ( src , element ) = src − hit-and-miss ( src , element ) \texttt{dst} = \mathrm{thining} ( \texttt{src} , \texttt{element} )= \texttt{src} - \texttt{hit-and-miss} ( \texttt{src} , \texttt{element} ) dst=thining(src,element)=src−hit-and-miss(src,element)
图像细化后依然保持图像的骨架且细小部分的连通性不变,细化将图像线条从多像素宽度减少至单位像素宽度。
opencv提供函数thinning() 用于图像的细化。其详细描述如下:
void cv::ximgproc::thinning(InputArray src, #输入图像 8bit单通道灰度图像
OutputArray dst, #输出图像 与输入图像同类型,同大小
int thinningType = THINNING_ZHANGSUEN #细化类型
)
Python:
cv.ximgproc.thinning(src[, dst[, thinningType]] )->dst
实现代码: C++
/**
* 图像细化 THINNING_ZHANGSUEN
* @param img
* @return
*/
int Morphological::thinning_ZHANGSUEN_transform(const Mat &img) {
logger_info("======hit_and_miss_transform=====================");
Mat dst;
ximgproc::thinning(img, dst, ximgproc::ThinningTypes::THINNING_ZHANGSUEN);
imshow("thinning zhangsuen", dst);
return EXIT_SUCCESS;
}
/**
* 图像细化 THINNING_GUOHALL
* @param img
* @return
*/
int Morphological::thinning_GUOHALL_transform(const Mat &img) {
logger_info("======thinning_GUOHALL_transform=====================");
Mat dst;
ximgproc::thinning(img, dst, ximgproc::ThinningTypes::THINNING_GUOHALL);
imshow("thinning guohall", dst);
return EXIT_SUCCESS;
}
实现代码: python
# thinning
def thinning_transform(origin_img):
# 非灰度图像转化维灰度图像
if origin_img.shape[2] == 3:
gray_image = cv.cvtColor(origin_img, cv.COLOR_BGR2GRAY)
else:
gray_image = origin_img
titles = ["origin_image"]
# images = [cv.cvtColor(gray_image, cv.COLOR_BGR2RGB)]
images = [gray_image]
dst = cv.ximgproc.thinning(gray_image, cv.ximgproc.THINNING_ZHANGSUEN)
images.append(dst)
titles.append("thinning_zhangsuen")
dst = cv.ximgproc.thinning(gray_image, cv.ximgproc.THINNING_GUOHALL)
images.append(dst)
titles.append("thinning_gauhall")
for i in range(3):
plt.subplot(1, 3, i + 1)
plt.imshow(images[i], 'gray', vmin=0, vmax=255)
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
细化效果图如图007-5所示
Thickening(粗化)
粗化是形态学的相对操作,细化前景色相当于粗化背景色。粗化操作由源图像与hit-and-miss处理后的并集组成。
dst = t h i n i n g ( src , element ) = src ∪ hit-and-miss ( src , element ) \texttt{dst} = \mathrm{thining} ( \texttt{src} , \texttt{element} )= \texttt{src} \cup \texttt{hit-and-miss} ( \texttt{src} , \texttt{element} ) dst=thining(src,element)=src∪hit-and-miss(src,element)
Skeletonization/Medial Axis Transform
骨架化指将二值图像的前景色(高亮部分)减少直至骨架的过程,该过程丢弃原始图像中大量的前景像素,但保留了图像的原始区域范围与连通性。
骨架化后的图像既然保留了原始图像的骨架及连通性,则在视觉处理中可大幅减少数据量的计算和对内存的占用。
实现代码: C++
/**
* 骨架
* @param img
* @return
*/
int Morphological::skeleton_transform(const Mat &img) {
logger_info("======skeleton_transform=====================");
imshow("image", img);
Mat dst;
// 图像阈值转化 灰度图像转二值图像
threshold(img, dst, 127, 255, THRESH_BINARY);
// 卷积核
Mat kernel = getStructuringElement(MORPH_CROSS, Size(7, 7));
Mat skel(dst.size(), CV_8UC1, cv::Scalar(0));
Mat temp(dst.size(), CV_8UC1);
bool done;
do {
cv::morphologyEx(dst, temp, MORPH_OPEN, kernel);
cv::bitwise_not(temp, temp);
cv::bitwise_and(dst, temp, temp);
cv::bitwise_or(skel, temp, skel);
cv::erode(dst, dst, kernel);
double max;
cv::minMaxLoc(dst, 0, &max);
done = (max == 0);
} while (!done);
imshow("skeleton", skel);
return EXIT_SUCCESS;
}
实现代码:python
# 骨架化
def skeleton_transform(origin_img):
# 非灰度图像转化维灰度图像
if origin_img.shape[2] == 3:
gray_image = cv.cvtColor(origin_img, cv.COLOR_BGR2GRAY)
else:
gray_image = origin_img
titles = ["origin_image"]
images = [gray_image]
ret, binary_img = cv.threshold(gray_image, 127, 255, cv.THRESH_BINARY)
titles.append("binary_image")
images.append(binary_img)
# 获取卷积核
kernel = cv.getStructuringElement(cv.MORPH_CROSS, (7, 7))
skeleton_img = np.zeros(binary_img.shape, np.uint8)
# tmp_img = np.zeros(binary_img.shape, np.uint8)
while True:
# 开运算
tmp_img = cv.morphologyEx(binary_img, cv.MORPH_OPEN, kernel)
tmp_img = cv.bitwise_not(tmp_img)
tmp_img = cv.bitwise_and(binary_img, tmp_img)
skeleton_img = cv.bitwise_or(skeleton_img, binary_img)
binary_img = cv.erode(binary_img, kernel)
min, max, _, _ = cv.minMaxLoc(binary_img)
if max == 0:
break
titles.append("skeleton_image")
images.append(skeleton_img)
for i in range(3):
plt.subplot(1, 3, i + 1)
plt.imshow(images[i], 'gray', vmin=0, vmax=255)
plt.title(titles[i])
plt.xticks(), plt.yticks([])
plt.show()
参考文献:
-
https://homepages.inf.ed.ac.uk/rbf/HIPR2/morops.htm
-
http://www.cs.cmu.edu/~cil/vision.html
-
https://docs.opencv.org/4.6.0/db/d06/tutorial_hitOrMiss.html
-
https://felix.abecassis.me/2011/09/opencv-morphological-skeleton/