用形态学的方法实现图像的角点检测的算法原理详解和代码实现(Pyton和C++代码)

​本篇博文基础知识请参考博文:https://blog.csdn.net/wenhao_ir/article/details/51888042
特别是其中的博文 https://blog.csdn.net/wenhao_ir/article/details/124763833
和博文 https://blog.csdn.net/wenhao_ir/article/details/125157967

在正式介绍用形态学的方法实现图像的角点检测的原理前,大家首先要知道以下两点:
①因为角点只是一个点,所以对角点的检测是在一个比较微小的结构进行的运算,我们应该在八邻域的尺度上来观察、分析和处理这个问题。
②在八邻域的尺度上角分为两类:
第一类为由水平边缘和垂直边缘形成的直角,在下文中我们姑且把它称为A型角。这种角的典形代表为下面这个角:
在这里插入图片描述
第二类为含45度斜线的角,在下文中我们姑且把它称为B型角。这种角的典型代表为下面这两个角:
在这里插入图片描述
好,有了以上准备知识,我们开始来介绍如何用形态学的方法实现图像的角点检测。

先说步骤,再具体分析为什么。
第01步—用十字形结构对原图像作膨胀处理得到图像①;
第02步—用菱形结构对图像①进行腐蚀操作得到图像②;
第03步—用X形结构对原图像作膨胀操作得到图像③;
第04步—用矩形结构对图像③进行腐蚀操作得到图像④;
第05步—用图像④减去图像②并求绝对值,得到角点的初步图像⑤;
第06步—对图像⑤作二值化阈值处理,得到真正的角点。

上面六步中,最难理解的是前五步,所以我们不妨将上面照片中的三种角代入具体的程序,看每一步作了怎样的处理。

下面两幅图中就含了上面提到的三种角。
请添加图片描述请添加图片描述
其中左边这幅图中的四个角为上面提到的A型角,右边这幅图中的三个角为上面提到的B型角。

先上完整的代码,然后观察每一步的运行结果:
代码中用到的两幅图像的下载地址如下:
https://pan.baidu.com/s/1L4hgDEqlElaOIw7rUvMBHA?pwd=hikt

# 博主微信/QQ 2487872782
# 有问题可以联系博主交流
# 有图像处理需求也请联系博主
# 图像处理技术交流QQ群 271891601

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# OpenCV的版本为4.1

import numpy as np
import cv2 as cv
import sys

# 读取图像
A1 = cv.imread('F:/material/images/Corner/A_corner.bmp', 0)
B1 = cv.imread('F:/material/images/Corner/B_corner.bmp', 0)
# 判断图片是否读取成功
if A1 is None:
    print('Error,Failed to read Image.')
    sys.exit()
if B1 is None:
    print('Error,Failed to read Image.')
    sys.exit()

# 第1步:十字形对原图进行膨胀操作
crossMat = cv.getStructuringElement(cv.MORPH_CROSS, (5, 5))
A1_step01 = cv.dilate(A1, crossMat)
B1_step01 = cv.dilate(B1, crossMat)

# 第2步:菱形对原图进行腐蚀操作
diamondMat = cv.getStructuringElement(cv.MORPH_CROSS, (5, 5))
diamondMat[1, 1] = 1
diamondMat[1, 3] = 1
diamondMat[3, 1] = 1
diamondMat[3, 3] = 1
A1_step02 = cv.erode(A1_step01, diamondMat)
B1_step02 = cv.erode(B1_step01, diamondMat)

# 第3步:X形对原图进行膨胀
xMat = np.eye(5, dtype='uint8')
xMat[0, 4] = 1
xMat[1, 3] = 1
xMat[3, 1] = 1
xMat[4, 0] = 1
A1_step03 = cv.dilate(A1, xMat)
B1_step03 = cv.dilate(B1, xMat)

# 第4步:正方形对上一步得的图像进行腐蚀
squareMat = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
A1_step04 = cv.erode(A1_step03, squareMat)
B1_step04 = cv.erode(B1_step03, squareMat)

# 第5步:用第4步得到的图像和第2步得到的图像作差值
A1_step05 = cv.absdiff(A1_step04, A1_step02)
B1_step05 = cv.absdiff(B1_step04, B1_step02)

# 第6步:把第5步得到的结果进行二值化处理
_, A1_step06 = cv.threshold(A1_step05, 100, 255, cv.THRESH_BINARY)

B1_step06 = B1_step05.copy()

# 由于B1_step05一个阈值无法将三个角点提取出来,所以这里分区域用不同的阈值进行处理。
B1_step06_1 = B1_step06[7:14, 24:31]
B1_step06_2 = B1_step06[36:42, 10:46]
_, B1_step06_1 = cv.threshold(B1_step06_1, 150, 255, cv.THRESH_BINARY)
_, B1_step06_2 = cv.threshold(B1_step06_2, 75, 255, cv.THRESH_BINARY)

B1_step06[7:14, 24:31] = B1_step06_1
B1_step06[36:42, 10:46] = B1_step06_2


cv.imshow("A1_step06", A1_step06)
cv.imshow("B1_step06", B1_step06)

cv.waitKey()

运行结果及分析如下:
首先我们来看两个原图中角点附近的数据情况:
A1图中的四个角实际上是一样的,所以我们只看其左上角的角:
在这里插入图片描述
再看B1中的三个角点的情况,由于B1中的左下角和右下角实际上是一样的,所以我们只看其最上面的角和左下的角即可。
在这里插入图片描述
在这里插入图片描述
下面我们来观察分析各步的运行结果:

第01步的运行结果及分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从第01步的运行结果可以看出,A型角没有被膨胀,而边缘和B型角都被膨胀了。

第02步的运行结果及分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从这一步的运行结果可以看出,相对于原图:A型角的角点被腐蚀了,而边缘和B型角经过这一步的腐蚀处理,恢复成原状了,这样在第02步得到的图像中便只包含B型角和边缘了。

第03步的运行结果及分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从第03步的运行结果来看,A型角和边缘被膨胀了,而B型角没有被膨胀。

扫描二维码关注公众号,回复: 14246852 查看本文章

第04步的运行结果及分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从第04步的运行结果来看,相对于原图,A型角和边缘恢复成原图那样,而B型角的角点被腐蚀了,这样在第04步得到的图像中便只包含A型角和边缘了。

第05步的运行结果及分析

在第02步得到的图像中包含B型角和边缘,在第04步得到的图像中包含A型角和边缘,我们用第02步和第04步的图像相减并求绝对值,就可以把边缘抵消,并且把A型角和B型角凸显出来,第05步便是这个操作。

第05步的运行结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从第 05步的结果可以看出,各角点附近的点都被检测出来了,这些有值的点作为角点其实我们都可以接受,但角点只有一个,所以我们下一步中我们需要用阈值进行二值化处理,从而选出唯一的角点。

第06步的运行结果及分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从第06步的运行结果可以看出,通过一系列形态学处理,第一幅图中的四个A型角点和第二幅图中的三个B型角点被检测出来。
总结一下:算法的第01步和第02步用于检测A型角点;第03步和第04步用于检测B型角点。

附C++代码

首先是适用于第一幅图的C++源码:

//博主微信/QQ 2487872782
//有问题可以联系博主交流
//有图像处理开发需求也请联系博主
//图像处理技术交流QQ群 271891601

//OpenCV版本:3.0.0
//VS版本:2013


#include <opencv2/opencv.hpp>

#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    
    
	cv::Mat srcImage = cv::imread("F:/material/images/Corner/A_corner.bmp");
	if (!srcImage.data)
		return 1;
	cv::Mat srcGray;
	cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	cv::imshow("srcGray", srcGray);
	// 定义结构元素
	Mat CrossMat(5, 5, CV_8U, Scalar(0));
	Mat diamondMat(5, 5, CV_8U, Scalar(1));
	Mat squareMat(5, 5, CV_8U, Scalar(1));
	Mat x(5, 5, CV_8U, Scalar(0));
	//  十字形形状  
	for (int i = 0; i<5; i++)
	{
    
    
		CrossMat.at<uchar>(2, i) = 1;
		CrossMat.at<uchar>(i, 2) = 1;
	}
	// 菱形形状
	diamondMat.at<uchar>(0, 0) = 0;
	diamondMat.at<uchar>(0, 1) = 0;
	diamondMat.at<uchar>(1, 0) = 0;
	diamondMat.at<uchar>(4, 4) = 0;
	diamondMat.at<uchar>(3, 4) = 0;
	diamondMat.at<uchar>(4, 3) = 0;
	diamondMat.at<uchar>(4, 0) = 0;
	diamondMat.at<uchar>(4, 1) = 0;
	diamondMat.at<uchar>(3, 0) = 0;
	diamondMat.at<uchar>(0, 4) = 0;
	diamondMat.at<uchar>(0, 3) = 0;
	diamondMat.at<uchar>(1, 4) = 0;
	// X形状
	for (int i = 0; i<5; i++){
    
    
		x.at<uchar>(i, i) = 1;
		x.at<uchar>(4 - i, i) = 1;
	}

	// 第1步:十字形对原图进行膨胀
	Mat cross_exp;
	dilate(srcGray, cross_exp, CrossMat);
	//cv::imshow("cross_exp", cross_exp);

	// 第2步:菱形对上步进行腐蚀
	Mat diamond_ero;
	erode(cross_exp, diamond_ero, diamondMat);
	//cv::imshow("diamond_ero", diamond_ero);

	// 第3步:X形对原图进行膨胀
	Mat X_shape_exp;
	dilate(srcGray, X_shape_exp, x);
	//cv::imshow("X_shape_exp", X_shape_exp);

	// 第4步:正方形对上步进行腐蚀
	Mat square_ero;
	erode(X_shape_exp, square_ero, squareMat);
	//cv::imshow("square_ero", square_ero);

	// 第5步:计算差值
	Mat result_diff;
	absdiff(square_ero, diamond_ero, result_diff);
	//cv::imshow("result_diff", result_diff);

	//第6步:阈值化处理
	Mat result_bin;
	threshold(result_diff, result_bin, 100, 255, THRESH_BINARY);
	//cv::imshow("result_bin", result_bin);
	
	// 绘图
	for (int i = 0; i < result_bin.rows; i++)
	{
    
    
		// 获取行指针
		const uchar* data = result_bin.ptr<uchar>(i);
		for (int j = 0; j < result_bin.cols; j++)
		{
    
    
			// 如果是角点 则进行绘制圆圈
			if (data[j])
				circle(srcGray, Point(j, i), 3,
				Scalar(255, 255, 255));
		}
	}
	cv::imshow("result", srcGray);
	cv::waitKey(0);
	return 0;
}

运行结果如下图所示:
在这里插入图片描述
再上适用于第二幅图的C++源码:

//博主微信/QQ 2487872782
//有问题可以联系博主交流
//有图像处理开发需求也请联系博主
//图像处理技术交流QQ群 271891601

//OpenCV版本:3.0.0
//VS版本:2013


#include <opencv2/opencv.hpp>

#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    
    
	cv::Mat srcImage = cv::imread("F:/material/images/Corner/B_corner.bmp");
	if (!srcImage.data)
		return 1;
	cv::Mat srcGray;
	cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	cv::imshow("srcGray", srcGray);
	// 定义结构元素
	Mat CrossMat(5, 5, CV_8U, Scalar(0));
	Mat diamondMat(5, 5, CV_8U, Scalar(1));
	Mat squareMat(5, 5, CV_8U, Scalar(1));
	Mat x(5, 5, CV_8U, Scalar(0));
	//  十字形形状  
	for (int i = 0; i<5; i++)
	{
    
    
		CrossMat.at<uchar>(2, i) = 1;
		CrossMat.at<uchar>(i, 2) = 1;
	}
	// 菱形形状
	diamondMat.at<uchar>(0, 0) = 0;
	diamondMat.at<uchar>(0, 1) = 0;
	diamondMat.at<uchar>(1, 0) = 0;
	diamondMat.at<uchar>(4, 4) = 0;
	diamondMat.at<uchar>(3, 4) = 0;
	diamondMat.at<uchar>(4, 3) = 0;
	diamondMat.at<uchar>(4, 0) = 0;
	diamondMat.at<uchar>(4, 1) = 0;
	diamondMat.at<uchar>(3, 0) = 0;
	diamondMat.at<uchar>(0, 4) = 0;
	diamondMat.at<uchar>(0, 3) = 0;
	diamondMat.at<uchar>(1, 4) = 0;
	// X形状
	for (int i = 0; i<5; i++){
    
    
		x.at<uchar>(i, i) = 1;
		x.at<uchar>(4 - i, i) = 1;
	}

	// 第1步:十字形对原图进行膨胀
	Mat cross_exp;
	dilate(srcGray, cross_exp, CrossMat);
	//cv::imshow("cross_exp", cross_exp);

	// 第2步:菱形对上步进行腐蚀
	Mat diamond_ero;
	erode(cross_exp, diamond_ero, diamondMat);
	//cv::imshow("diamond_ero", diamond_ero);

	// 第3步:X形对原图进行膨胀
	Mat X_shape_exp;
	dilate(srcGray, X_shape_exp, x);
	//cv::imshow("X_shape_exp", X_shape_exp);

	// 第4步:正方形对上步进行腐蚀
	Mat square_ero;
	erode(X_shape_exp, square_ero, squareMat);
	//cv::imshow("square_ero", square_ero);

	// 第5步:计算差值
	Mat result_diff;
	absdiff(square_ero, diamond_ero, result_diff);
	//cv::imshow("result_diff", result_diff);

	//第6步:阈值化处理,一个阈值无法将三个角点提取出来,所以这里分区域用不同的阈值进行处理。
	Mat result_bin = result_diff.clone();
	Mat result_bin_roi_1 = result_bin(cv::Rect(24, 6, 7, 7));
	Mat result_bin_roi_2 = result_bin(cv::Rect(10, 36, 36, 6));
	threshold(result_bin_roi_1, result_bin_roi_1, 150, 255, THRESH_BINARY);
	threshold(result_bin_roi_2, result_bin_roi_2, 75, 255, THRESH_BINARY);

	result_bin_roi_1.copyTo(result_bin(cv::Rect(24, 6, 7, 7)));
	result_bin_roi_2.copyTo(result_bin(cv::Rect(10, 36, 36, 6)));

	//cv::imshow("result_bin", result_bin);

	// 绘图
	for (int i = 0; i < result_bin.rows; i++)
	{
    
    
		// 获取行指针
		const uchar* data = result_bin.ptr<uchar>(i);
		for (int j = 0; j < result_bin.cols; j++)
		{
    
    
			// 如果是角点 则进行绘制圆圈
			if (data[j])
				circle(srcGray, Point(j, i), 3,
				Scalar(255, 255, 255));
		}
	}
	cv::imshow("result", srcGray);
	cv::waitKey(0);
	return 0;
}

运行结果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wenhao_ir/article/details/125177651