OpenCV实现击中击不中变换和形态学细化

2023-05-16

1 击中击不中变换

1.1 HMT概述

形态学Hit-or-Miss是形状检测基本工具,只要结构元设置得当,就可以检测一些基本的形状图案,HMT变换只能作用于二值图像,结构元(核)元素值由0、1、-1组成。

操作时,结构元在图像上滑动,覆盖一小片与核大小一样的区域,然后逐一对比,核的值为1时,覆盖区域对应位置必须为255,而核值为-1时,则必须为0,核值为0时0和255均可,如果覆盖区域所有的位置均满足上述要求,则表示击中,锚点位置设置为255,如果有任意一个位置不满足,则表示击不中,锚点位置设置为0。

不过HMT单独用来作为通俗意义上的形状检测并不是很常用(个人理解),一般都是作为其他形态学算法的基础,例如:凸壳、细化、骨架化等。

1.2 代码实现

下面简单演示一下HMT的简单实现代码和固定尺寸矩形的检测

//func:			击中击不中变换:Hitmiss-----> 核中-1与图像中0对应,1与255对应,0任意对应
//matpadded:	输入已进行边界扩展的二值化图像
cv::Mat _hit_or_miss(const cv::Mat& matpadded, const cv::Mat& kernel)
{
	CV_Assert(matpadded.type() == CV_8UC1);
	CV_Assert(kernel.type() == CV_32SC1);			//含有负数,选int类型

	int rows = matpadded.rows - kernel.rows + 1;
	int cols = matpadded.cols - kernel.cols + 1;
	cv::Mat mat = cv::Mat::zeros(rows, cols, CV_8UC1);

	for (int i = 0; i < mat.rows; ++i)
	{
		for (int j = 0; j < mat.cols; ++j)
		{
			bool isGood = true;
			cv::Mat roi(matpadded, cv::Rect(j, i, kernel.cols, kernel.rows));
			//进行击中击不中判断
			for (int ii = 0; ii < kernel.rows; ++ii)
			{
				uchar* roi_p = roi.ptr<uchar>(ii);
				const int* kernel_p = kernel.ptr<int>(ii);

				for (int jj = 0; jj < kernel.cols; ++jj)
				{
					if ((kernel_p[jj] == 1 && roi_p[jj] == 0) || (kernel_p[jj] == -1 && roi_p[jj] == 255))
					{
						isGood = false;
						break;
					}
				}
				if (!isGood)
					break;
			}

			mat.ptr<uchar>(i)[j] = (isGood ? 255 : 0);
		}
	}

	return mat;
}


/*
在测试图像中创建若干个4X4填充矩形,和边长大于4的矩形,利用HMT对4x4矩形进行检测
结构元设置为{-1,-1,-1,-1,-1,-1
            -1,1,1,1,1,-1
            -1,1,1,1,1,-1
            -1,1,1,1,1,-1
            -1,1,1,1,1,-1
            -1,-1,-1,-1,-1,-1};
即在矩形的外圈填充一圈-1,以此来确定矩形的边界。
*/

int main()
{
    cv::Mat kernel = (cv::Mat_<int>(6, 6) <<
		-1, -1, -1, -1, -1, -1,
		-1, 1, 1, 1, 1, -1,
		-1, 1, 1, 1, 1, -1,
		-1, 1, 1, 1, 1, -1,
		-1, 1, 1, 1, 1, -1,
		-1, -1, -1, -1, -1, -1);
	cv::Size shapeSize = kernel.size();
	cv::Mat matPadded;
	cv::Mat myHitmiss, opencvHitmiss;

	//边界拓展的原则是:如果锚点在核中心,当核的尺寸为偶数时,左和上边界要比其他两边界多1
	int left = shapeSize.width / 2;
	int right = shapeSize.width % 2 == 0 ? shapeSize.width / 2 - 1 : shapeSize.width / 2;
	int top = shapeSize.height / 2;
	int bottom = shapeSize.height % 2 == 0 ? shapeSize.height / 2 - 1 : shapeSize.height / 2;

	//创建测试图像
	cv::Mat test = cv::Mat::zeros(300, 300, CV_8UC1);
	cv::rectangle(test, cv::Rect(20, 20, 4, 4), cv::Scalar(255), cv::FILLED);
	cv::rectangle(test, cv::Rect(50, 20, 4, 4), cv::Scalar(255), cv::FILLED);
	cv::rectangle(test, cv::Rect(20, 60, 4, 4), cv::Scalar(255), cv::FILLED);
	cv::rectangle(test, cv::Rect(60, 20, 4, 4), cv::Scalar(255), cv::FILLED);
	cv::rectangle(test, cv::Rect(80, 40, 6, 6), cv::Scalar(255), cv::FILLED);
	cv::rectangle(test, cv::Rect(100, 80, 10, 8), cv::Scalar(255), cv::FILLED);

	cv::copyMakeBorder(test, matPadded, top, bottom, left, right, cv::BORDER_REFLECT101);

	myHitmiss = _hit_or_miss(matPadded, kernel);
	cv::morphologyEx(test, opencvHitmiss, cv::MORPH_HITMISS, kernel);

	cv::imshow("myhitmiss", myHitmiss);
	cv::imshow("opencvHitmiss", opencvHitmiss);

	cv::waitKey(0);
	return 0;
}

1.3 结果展示

在结果图中形成了一些小白点,即检测到的规定尺寸大小矩形的中心

在这里插入图片描述


2 形态学细化

2.1 概述

在众多博客中看到了各种各样的细化版本,本人学识较浅,实在是读不懂别人的代码,就花了一点时间理解了一下冈萨雷斯《数字图像处理》形态学细化篇章,并做了一个代码实现。

形态学细化由我的理解来说就是:利用前辈们总结的一组结构元,不断循环重复的进行HMT变换,直至结果收敛(不在变换),单次细化公式定义为:
在这里插入图片描述
其中 A A A为源图像, B B B为结构元,编程时主要采用中间哪项定义即:用输出图 = 源图像 - 结构元对源图像进行HMT变换的结果。

其中 B B B为:
在这里插入图片描述
依据这一结构元序列将细化定义为:
在这里插入图片描述
这一处理过程就是 A A A B 1 B^1 B1细化一次,得到的结果然后被 B 2 B^2 B2细化一次,以此类推,一直套娃下去,直至得到的结果不在出现变化为止。

在这里插入图片描述
对于图片中的结构元,黑色代表前景值为1,白色为背景值为-1,x的值为0.

2.2 代码实现

只做简单实现,耗时在100ms左右。

//单次图像细化
//输入二值化图像
static void Morph_Thinning(const cv::Mat& src, const cv::Mat& kernel, cv::Mat& dst)
{
	CV_Assert(src.type() == CV_8UC1);
	CV_Assert(kernel.type() == CV_32SC1);

	cv::Mat tmpdst;
	cv::morphologyEx(src, tmpdst, cv::MORPH_HITMISS, kernel);
	dst = src - tmpdst;
}

/*
对一副图像计算梯度幅值图像,将二值化后的幅值图像进行细化
*/

int main()
{
	std::string path = "F:\\NoteImage\\扑克牌2.jpg";

	cv::Mat src = cv::imread(path, cv::IMREAD_GRAYSCALE);
	if (!src.data) {
		std::cout << "Could not open or find the image" << std::endl;
		return -1;
	}

	cv::Mat dx, dy;
	cv::Sobel(src, dx, CV_32FC1, 1, 0);
	cv::Sobel(src, dy, CV_32FC1, 0, 1);

	cv::Mat mag;
	cv::magnitude(dx, dy, mag);
	cv::normalize(mag, mag, 0, 255, cv::NORM_MINMAX);
	mag.convertTo(mag, CV_8UC1);

	cv::Mat thres;
	cv::threshold(mag, thres, 50, 255, cv::THRESH_BINARY);

	//创建结构元序列
	std::vector<std::vector<int>> Kernel_array = {
		{-1,-1,-1,0,1,0,1,1,1},
		{0,-1,-1,1,1,-1,1,1,0},
		{1,0,-1,1,1,-1,1,0,-1},
		{1,1,0,1,1,-1,0,-1,-1},
		{1,1,1,0,1,0,-1,-1,-1},
		{0,1,1,-1,1,1,-1,-1,0},
		{-1,0,1,-1,1,1,-1,0,1},
		{-1,-1,0,-1,1,1,0,1,1}
	};

	std::vector<cv::Mat> kernels(Kernel_array.size());
	for (int i = 0; i < Kernel_array.size(); ++i)
	{
		cv::Mat kernel = cv::Mat(Kernel_array[i]).reshape(0, 3);
		kernels[i] = kernel;
	}

	int iterations = 0;					//迭代次数
	int equalCount = 0;					//收敛次数
	const int Max_Iterations = 100;		//最大迭代次数

	cv::Mat dst = thres.clone();

	double t = cv::getTickCount();

	while (iterations < Max_Iterations)
	{
		cv::Mat tempdst = dst.clone();
		const int index = iterations % 8;

		Morph_Thinning(dst, kernels[index], dst);
        //判断这一次的结果和上一次是否相等
		cv::Mat diff = (tempdst != dst);
		bool equal = (cv::countNonZero(diff) == 0);

		if (equal)
			equalCount++;
		else
			equalCount = 0;

		//收敛次数超过两次退出
		if (equalCount > 2)
			break;

		iterations++;
	}
	//计算时间打印结果
	double timepass = (cv::getTickCount() - t) / cv::getTickFrequency();
	std::cout << iterations << std::endl;
	std::cout << timepass << std::endl;

	cv::imshow("Thinning_dst", dst);
	cv::waitKey(0);
	return 0;
}

2.3 实验结果

在这里插入图片描述
看的出来边缘被明显细化,但要达到真正的1像素宽边缘,还要将细化后元素转化为m连通,这一步骤下期再见!

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OpenCV实现击中击不中变换和形态学细化 的相关文章

随机推荐

  • 更改conda install、pip install源

    由于tensorflow暂不支持python3 7 xff0c 因此博主需要将python3 7降回到python3 6 xff0c 但是默认源下载实在太慢 转载于 https blog csdn net dream allday arti
  • VMware虚拟机配置、连接MobaXterm

    连接MobaXterm 在上一篇博客中详细讲述了 VMware 虚拟机的安装 xff0c 这里演示一下虚拟机连接 MobaXterm xff0c 虚拟机连接 MobaXterm 以后就可以直接在 MobaXterm 里面执行虚拟机操作 xf
  • 详细描述虚拟机安装JDK并配置环境变量的全过程

    Linux虚拟机安装JDK步骤 1 登录虚拟机以后查看防火墙状态 xff0c 要求关闭防火墙 查看防火墙指令 xff1a systemctl status firewalld 禁用防火墙指令 xff1a systemctl disable
  • 多线程经典练习题

    线程练习题 1 用共享资源的方式实现 生产者 仓库 消费者的模式 注意线程安全 线程通信 span class token keyword public span span class token keyword class span sp
  • 目标检测1——YOLO数据标注以及xml转为txt文件脚本实战

    目录 1 数据标注 2 xml批量转为txt文件 1 数据标注 利用Yolov5进行目标检测的过程中 我们需要对数据进行标注 这里我们利用的是labelImg脚本进行数据标注的 labelImg主要用于yolov5数据标注工具 深度学习文档
  • JSON数据清理(详解)

    二 JSON数据清洗 1 JSON数据 仅以两条数据为例 span class token number 1593136280858 span span class token operator span span class token
  • Spark-SQL常用内置日期时间函数

    Spark SQL常用内置日期时间函数 一 获取当前时间 1 current date 获取当前日期时间格式 xff1a yyyy MM dd spark span class token punctuation span span cla
  • Java连接hive

    注意 xff1a 需要开启hive服务 首先建一个maven工程 xff0c 导入依赖包导pom xml span class token tag span class token tag span class token punctuat
  • 基于 Mapnik 的地图服务器

    目录 一 简介二 安装 PostgreSQL 数据库和 PostGIS 扩展三 下载地图样式表和上传地图数据四 将地图数据导入 PostgresSQL五 生成 Mapnik Stylesheet六 安装 mapnik七 地图生成1 安装 E
  • Linux 添加互信

    一 添加主机列表 span class token function vi span etc hosts 添加内容 ip1 hoatnmae1 ip2 hoatnmae2 ip3 hoatnmae3 二 秘钥分发 2 1 生成秘钥 ssh
  • Python升级后 yum 无法正常使用

    问题 xff1a 原因 xff1a 这是因为 yum 采用 Python 作为命令解释器 xff0c 这可以从 usr bin yum 文件中第一行 usr bin python 发现 而 python 版本之间兼容性不太好 xff0c 使
  • CDH开启高可用后,NameNode主备节点切换

    查看nn1的状态 hdfs haadmin getServiceState nn1 span class token comment standby span hdfs haadmin getServiceState nn2 span cl
  • git分支从master切换到main

    git分支从master切换到main 背景 本地当前分支为master xff0c 远程仓库为main xff0c 且远程仓库与本地仓库有 unrelated histories这样的问题 xff0c 如远程仓库有README md但本地
  • Docker学习第三天——Docker网络

    文章目录 摘要Linux 网络命名空间Docker bridge网络容器之间的Link容器的端口映射容器网络之host和none多容器复杂应用的部署多机器通信 摘要 Docker学习之旅第三天 Docker 网络 看完这篇文章将收获dock
  • 大津阈值法(OTSU)功能实现

    具体的公式推导参见冈萨雷斯 数字图像处理 Otsu方法又称最大类间方差法 xff0c 通过把像素分配为两类或多类 xff0c 计算类间方差 xff0c 当方差达到最大值时 xff0c 类分割线 xff08 即灰度值 xff09 就作为图像分
  • cas-server服务端搭建

    1 下载cas服务代码 xff0c https github com apereo cas overlay template tree 5 3 2 使用idea工具打开cas overlay template 5 3包 xff0c 使用ma
  • 自适应阈值canny边缘检测(功能实现)

    学习记录 1 概述 canny边缘检测是一种特别常用且性能优秀的边缘检测算法 xff0c 相比于普通的边缘检测算法 xff0c canny获得的边缘较细且具有连续的边缘轮廓 xff0c 为之后的一系列图像处理带来极大的便利 canny边缘检
  • OpenCV实现图像基础频率域滤波

    写在前面 xff1a 刚开始接触数字图像处理频率域滤波时 xff0c 很是陌生 xff0c 感觉这个技术使用范围很窄 xff0c 不如空域直接处理来的实在 xff0c 最近看书发现有些情况又不得不在频率域中进行操作 xff0c 个人感觉图像
  • OpenCV实现同态滤波

    同态滤波是属于图像增强的一个小算法 xff0c 其原理和代码实现在众多博客中均有提及 xff0c 再此 xff0c 只对学习中一些自认为有用的知识点进行总结 实现和学习过程中的一些总结 xff1a 同态滤波类似于灰度变换 xff0c 都是对
  • OpenCV实现击中击不中变换和形态学细化

    1 击中击不中变换 1 1 HMT概述 形态学Hit or Miss是形状检测基本工具 xff0c 只要结构元设置得当 xff0c 就可以检测一些基本的形状图案 xff0c HMT变换只能作用于二值图像 xff0c 结构元 xff08 核