hit-miss变换实例

简介: 本文依旧延续斯坦福的课程,讲解下如何用形态学操作来辨识钥匙。给定一把钥匙,如何从一串钥匙中匹配该钥匙,每把钥匙尾部都不同,但两幅图像中钥匙尺寸都相同。

本文依旧延续斯坦福的课程,讲解下如何用形态学操作来辨识钥匙。给定一把钥匙,如何从一串钥匙中匹配该钥匙,每把钥匙尾部都不同,但两幅图像中钥匙尺寸都相同。虽然还没想到它的经济价值,但是还是比较有趣的。

介绍内容:

1.      题目

2.      解题思路

3.      Matlab代码及实验结果

4.      击中不击中原理及opencv实现


老外原题:

DuplicateKey Detection

  In a large set of keys, we would like to use image processing to automatically detectif two keys are duplicates of each other. Two keys can be considered duplicateseven if they have different bows, as long as their blades are identical.

  Design and implement an image processing algorithm to automatically determinewhich key (if any) in hw3_keys_set.png is a duplicate of the key in hw3_key.png.


题目大意就是根据Blade找匹配的钥匙【不要管bow】

 

解题思路:

1.      二值化钥匙串的图片key_set

2.      二值化钥匙目标图片key,并对二值图像进行剪切,只保留blade部分【这算开挂!】

3.      对二值化目标图片的前景部分【白色】,使用1*3结构基元腐蚀

4.      对于二值化目标图像的背景部分【黑色】,分别使用1*7结构基元和1*5结构基元膨胀,然后计算他们的差分图像【3,4两步其实是计算hit-miss变换的两种模板】

5.      对钥匙串图片进行hit-miss操作【算法步骤后面介绍】

6.      对5的结果,用目标图片进行膨胀。

7.      展示结果,显示钥匙串的二值化图,通过阈值改变匹配的blade部分灰度值。

Matlab源码:

clc; clear all;
% Load and binarize test image
imgTest = im2double(imread('hw3_keys_set.png'));
imgTestGray = rgb2gray(imgTest);
imgTestBin = double(imgTestGray < 0.95);
% figure(1); clf;
% imshow(imgTestBin);
% Load and binarize template image
imgTemplate = im2double(imread('hw3_key.png'));
imgTemplateGray = rgb2gray(imgTemplate);
imgTemplateBin = double(imgTemplateGray < 0.95);
% figure(2); clf;
% imshow(imgTemplateBin);
% Prepare structuring elements
imgTemplateBin = imgTemplateBin(93:end,:);
SE1 = imerode(imgTemplateBin, ones(1,3));
SE2 = imdilate(imgTemplateBin, ones(1,7)) - imdilate(imgTemplateBin, ones(1,5));
% SE2 = imdilate(imgTemplateBin, ones(1,5));
figure(3); clf;
imshow(SE1);
figure(4); clf;
imshow(SE2);
% Perform hit-miss filtering
imgHitMiss = bwhitmiss(imgTestBin, SE1, SE2);
imgHitMissDilate = imdilate(imgHitMiss, imgTemplateBin);
figure(5); clf;
alpha = 0.3;
imshow(max(imgHitMissDilate, alpha*imgTestBin));

实验效果:







Hit-miss算法步骤:

击中击不中变换是形态学中用来检测特定形状所处位置的一个基本工具。它的原理就是使用腐蚀;如果要在一幅图像A上找到B形状的目标,我们要做的是:

首先,建立一个比B大的模板W;使用此模板对图像A进行腐蚀,得到图像假设为Process1;

其次,用B减去W,从而得到V模板(W-B);使用V模板对图像A的补集进行腐蚀,得到图像假设为Process2;

然后,Process1与Process2取交集;得到的结果就是B的位置。这里的位置可能不是B的中心位置,要视W-B时对齐的位置而异;

其实很简单,两次腐蚀,然后交集,结果就出来了。


Hit-miss原理:

基于腐蚀运算的一个特性:腐蚀的过程相当于对可以填入结构元素的位置作标记的过程。

    腐蚀中,虽然标记点取决于原点在结构元素中的相对位置,但输出图像的形状与此无关,改变原点的位置,只会导致输出结果发生平移。

    既然腐蚀的过程相当于对可以填入结构元素的位置作标记的过程,可以利用腐蚀来确定目标的位置。

    进行目标检测,既要检测到目标的内部,也要检测到外部,即在一次运算中可以同时捕获内外标记。

    由于以上两点,采用两个结构基元H、M,作为一个结构元素对B=(H,M),一个探测目标内部,一个探测目标外部。当且仅当H平移到某一点可填入X的内部,M平移到该点可填入X的外部时,该点才在击中击不中变换的输出中。

 

Hit-miss示意图:

在A图中寻找B图所示的图像目标的位置。


解:

1、确定结构元素

既然是寻找图B所示形状,选取H为图B所示的形状。再选一个小窗口W,W包含H,M=W-H。如下图所示:


2、求


3、求


4、求



基于Opencv的实现:

// hist_miss.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "cv.h"
#include "highgui.h"

int _tmain(int argc, _TCHAR* argv[])
{
	IplImage *src,*temp,*g_src,*g_temp,*g_blade,*e_blade,*b_blade,*b_blade_cha1,*b_blade_cha2;
	int i,j,pixel;

	src = cvLoadImage("b.png");
	temp = cvLoadImage("a.png");

	g_src = cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1);
	g_temp = cvCreateImage(cvSize(temp->width,temp->height),IPL_DEPTH_8U,1);
	g_blade = cvCreateImage(cvSize(temp->width,temp->height - 93),IPL_DEPTH_8U,1);
	e_blade = cvCreateImage(cvSize(temp->width,temp->height - 93),IPL_DEPTH_8U,1);
	b_blade = cvCreateImage(cvSize(temp->width,temp->height - 93),IPL_DEPTH_8U,1);
	b_blade_cha1 = cvCreateImage(cvSize(temp->width,temp->height - 93),IPL_DEPTH_8U,1);
	b_blade_cha2 = cvCreateImage(cvSize(temp->width,temp->height - 93),IPL_DEPTH_8U,1);


	//彩色图转灰度图
	cvCvtColor(src,g_src,CV_BGR2GRAY);
	cvCvtColor(temp,g_temp,CV_BGR2GRAY);

	//二值化操作
	for (i=0;i<src->height;i++)
	{
		for (j=0;j<src->width;j++)
		{
			pixel = (unsigned char)g_src->imageData[i*src->width+j];
			if (pixel < 255*0.95)
			{
				g_src->imageData[i*src->width+j] = 255;
			}else{
				g_src->imageData[i*src->width+j] = 0;
			}
		}
	}

	for (i=0;i<temp->height;i++)
	{
		for (j=0;j<temp->width;j++)
		{
			pixel = (unsigned char)g_temp->imageData[i*temp->width+j];
			if (pixel < 255*0.95)
			{
				g_temp->imageData[i*temp->width+j] = 255;
			}else{
				g_temp->imageData[i*temp->width+j] = 0;
			}
		}
	}

	//仅保留blade部分,开挂了
	for (i=92;i<temp->height;i++)
	{
		for (j=0;j<temp->width;j++)
		{
			g_blade->imageData[(i-92)*temp->width+j] = g_temp->imageData[i*temp->width+j];
		}
	}

	//准备结构元素
	IplConvKernel *u1,*u2,*u3;
	int value1[3]={1,1,1};
	int value2[7]={1,1,1,1,1,1,1};
	int value3[5]={1,1,1,1,1};
	u1 =  cvCreateStructuringElementEx(3,1,1,0,CV_SHAPE_RECT,value1);
	u2 =  cvCreateStructuringElementEx(7,1,3,0,CV_SHAPE_CUSTOM,value2);
	u3 =  cvCreateStructuringElementEx(5,1,2,0,CV_SHAPE_CUSTOM,value3);

	//对Blade腐蚀
	cvErode(g_blade,e_blade,u1);

	//对Blade膨胀
	cvDilate(g_blade,b_blade_cha2,u3);
	cvDilate(g_blade,b_blade_cha1,u2);
	

	//膨胀后两幅图像相减
	for (i=0;i<b_blade_cha1->height;i++)
	{
		for (j=0;j<b_blade_cha1->width;j++)
		{
			b_blade->imageData[i*temp->width+j] = b_blade_cha1->imageData[i*temp->width+j] - b_blade_cha2->imageData[i*temp->width+j];
		}
	}

	//准备结构元素
	IplImage *src_1,*src_2,*src_3;
	src_1 = cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1);
	src_2 = cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1);
	src_3 = cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1);
	int *value_e,*value_b;

	value_e = (int *)malloc(sizeof(int)*e_blade->height*e_blade->width);
	value_b = (int *)malloc(sizeof(int)*e_blade->height*e_blade->width);
	memset(value_e,0,sizeof(int)*e_blade->height*e_blade->width);
	memset(value_b,0,sizeof(int)*e_blade->height*e_blade->width);

	for (i=0;i<e_blade->height;i++)
	{
		for (j=0;j<e_blade->width;j++)
		{
			if ((unsigned char)e_blade->imageData[i*e_blade->width+j] == 255)
			{
				value_e[i*e_blade->width+j] =1;
			}
			if ((unsigned char)b_blade->imageData[i*e_blade->width+j] == 255)
			{
				value_b[i*e_blade->width+j] =1;
			}
		}
	}
	
	IplConvKernel *e,*b;
	e = cvCreateStructuringElementEx(e_blade->width,e_blade->height,e_blade->width/2,e_blade->height/2,CV_SHAPE_CUSTOM,value_e);
	b = cvCreateStructuringElementEx(e_blade->width,e_blade->height,e_blade->width/2,e_blade->height/2,CV_SHAPE_CUSTOM,value_b);
	
	//分别使用e和b对g_src和~g_src腐蚀
	cvErode(g_src,src_1,e);

	cvNot(g_src,src_2);	//取反

	cvErode(src_2,src_3,b);

	cvAnd(src_1,src_3,src_2);//取交集

	//使用g_blade,对交集部分膨胀
	int *value_blade;
	value_blade = (int *)malloc(sizeof(int)*g_blade->width*g_blade->height);
	memset(value_blade,0,sizeof(int)*g_blade->width*g_blade->height);

	//int count =0;
	for (i=0;i<g_blade->height;i++)
	{
		for (j=0;j<g_blade->width;j++)
		{
			if ((unsigned char)g_blade->imageData[i*g_blade->width+j] == 255)
			{
				value_blade[i*g_blade->width+j] = 1;
			}
		}
	}
	IplConvKernel *and;
	and = cvCreateStructuringElementEx(e_blade->width,e_blade->height,e_blade->width/2,e_blade->height/2,CV_SHAPE_CUSTOM,value_blade);
	cvDilate(src_2,src_3,and);

	
	//修改g_src,以最终显示
	for (i=0;i<g_src->height;i++)
	{
		for (j=0;j<g_src->width;j++)
		{
			pixel = (unsigned char)src_3->imageData[i*g_src->width+j];
			
			if (!pixel)
			{
				g_src->imageData[i*g_src->width+j] = 0.3*(unsigned char)g_src->imageData[i*g_src->width+j];
			}else{
				g_src->imageData[i*g_src->width+j] = g_src->imageData[i*g_src->width+j];
			}
		}
	}

	//cvNamedWindow("src",0);
	cvNamedWindow("template",0);
	//cvShowImage("src",b_blade);
	cvShowImage("template",src_3);
	//cvSaveImage("src_2.jpg",src_2);
	cvWaitKey(0);

	return 0;
}

结果很不好,关键是最后一步用blade去膨胀src_2时,结果图像居然是倒过来的!同样两幅图,我用matlab的imdilate函数,结果是正的。

网上看了下,可能这是Opencv中cvCreateStructuringElementEx自身的BUG,主要是anchor的问题,而且直到2.4.2都没解决!

http://answers.opencv.org/question/5014/how-to-use-the-function/

唉,坑了!下面贴出悲剧的结果:

src_2.jpg:【其实没啥,就3个像素点】


g_blade.jpg:


膨胀后的结果,src_3.jpg:


最后的结果:【不对的!】


目录
相关文章
|
Java 测试技术 网络安全
PTS报错问题之压测报错如何解决
PTS(Performance Testing Service)是一项面向网站、应用等提供的压力测试服务,用于模拟不同场景下的用户访问,评估系统的性能表现;在进行PTS压测时,可能会出现一些异常或报错,本合集将PTS压测中频繁出现的问题及其解决办法进行汇编,旨在帮助用户更有效地进行性能测试和问题定位。
|
Web App开发 JavaScript Java
浏览器同域名请求的最大并发数限制
  当我们在浏览网页的时候,对浏览速度有一个重要的影响因素,就是浏览器的并发数量。并发数量简单通俗的讲就是,当浏览器网页的时候同时工作的进行数量。   如果同时只有2个并发连接数数量,那网页打开的时候只能依赖于这2条线程,前面如果有打开慢的内容,就会直接影响到后面的内容打开。
6799 0
|
存储 运维 Oracle
国产数据库:目前最火的五款国产数据介绍
随着互联网的高速发展,目前数据的存储越来越多,传统的数据库逐渐不能满足人们对海量数据、高效查询的需求,国产的数据库如雨后春笋一样,一个个冒了出来来解决我们高速科技发展的数据库瓶颈,今天就给大家聊一聊目前最火的五款国产数据库,大家一起来交流一下。
国产数据库:目前最火的五款国产数据介绍
|
11月前
|
数据采集 人工智能 安全
数据治理的实践与挑战:大型案例解析
在当今数字化时代,数据已成为企业运营和决策的核心资源。然而,随着数据量的爆炸性增长和数据来源的多样化,数据治理成为了企业面临的重要挑战之一。本文将通过几个大型案例,探讨数据治理的实践、成效以及面临的挑战。
1497 4
数据治理的实践与挑战:大型案例解析
|
监控 物联网 数据挖掘
云上智能工厂:重塑制造业的未来生态
云上智能工厂将不断提升智能化水平,包括生产流程的自动化、管理决策的智能化等方面。这将进一步提高生产效率和质量,降低成本和浪费。 绿色可持续发展:随着全球对环保问题的日益关注,云上智能工厂将更加注重绿色生产和可持续发展。通过优化生产流程、更新节能设备等方式降低能耗和排放,实现绿色生产目标。
|
API Android开发 开发者
Android经典实战之用WindowInsetsControllerCompat方便的显示和隐藏状态栏和导航栏
本文介绍 `WindowInsetsControllerCompat` 类,它是 Android 提供的一种现代化工具,用于处理窗口插入如状态栏和导航栏的显示与隐藏。此类位于 `androidx.core.view` 包中,增强了跨不同 Android 版本的兼容性。主要功能包括控制状态栏与导航栏的显示、设置系统窗口行为及调整样式。通过 Kotlin 代码示例展示了如何初始化并使用此类,以及如何设置系统栏的颜色样式。
586 2
|
算法 Python
群智能算法:【WOA】鲸鱼优化算法详细解读
本文详细解读了鲸鱼优化算法(WOA),这是一种受鲸鱼捕食行为启发的新兴群体智能优化算法,具有强大的全局搜索能力和快速收敛速度。文章分为五个部分,分别介绍了引言、算法原理、主要步骤、特点及Python代码实现。通过模拟鲸鱼的捕食行为,该算法能够在复杂的优化问题中找到全局最优解。
|
机器学习/深度学习 搜索推荐 数据挖掘
【深度解析】超越RMSE和MSE:揭秘更多机器学习模型性能指标,助你成为数据分析高手!
【8月更文挑战第17天】本文探讨机器学习模型评估中的关键性能指标。从均方误差(MSE)和均方根误差(RMSE)入手,这两种指标对较大预测偏差敏感,适用于回归任务。通过示例代码展示如何计算这些指标及其它如平均绝对误差(MAE)和决定系数(R²)。此外,文章还介绍了分类任务中的准确率、精确率、召回率和F1分数,并通过实例说明这些指标的计算方法。最后,强调根据应用场景选择合适的性能指标的重要性。
1356 0
|
弹性计算 Rust 监控
云服务器 ECS产品使用问题之如何在阿里云幻兽帕鲁服务器中添加Mod
云服务器ECS(Elastic Compute Service)是各大云服务商阿里云提供的一种基础云计算服务,它允许用户租用云端计算资源来部署和运行各种应用程序。以下是一个关于如何使用ECS产品的综合指南。
|
SQL 关系型数据库 MySQL
DataX - 全量数据同步工具(1)
DataX - 全量数据同步工具