OpenCV入门(八)——形态学技术_opencv形态学-程序员宅基地

技术标签: c++  OpenCV  计算机视觉  形态学  图像处理  opencv  

目录

0x01 腐蚀膨胀操作

0x02 开闭运算操作

0x03 形态学梯度

0x04 形态学Top-Hat

0x05 用在哪?角点提取、车牌提取


数学形态学是基于集合论的图像处理方法,最早出现于生物学的形态与结构中,图像处理中的形态学操作用于图像预处理操作(去噪、形状简化)、图像增强(骨架提取、细化、凸包及物体标记)、物体背景分割物体形态量化等场景中。数学形态学利用点集的性质、积分几何集及拓扑学理论对物体像素集进行变换。

那么操作有如下:

  • 腐蚀与膨胀操作

  • 开闭运算操作及实现

  • 形态学梯度操作

  • 形态学TOP-Hat

0x01 腐蚀膨胀操作

膨胀操作时形态学的基本操作,实现了对目标像素点进行扩展的目的,其定义如下:

 形态学膨胀操作的思路:

运算前A与B分别为两个区域,B区域的黑点表示B的中心点,非B表示B相对于自己中心对称变换后的图形,运算后相当于B对称,沿着区域A的边界遍历一圈,区域B的中心扫过区域加上A本身的区域就是区域A膨胀区域B的结果,其中(B)z表示将B平移,使其中心点位于z位置。

腐蚀操作也是形态学的基本操作,实现了对目标像素点进行缩小的目的,其定义:

 腐蚀操作思路:

运算前A与B分别为两个区域,使用B对A进行腐蚀就是沿着区域A的内部边界遍历一圈,平移区域B形成的集合区。B的中心形成的轨迹即是腐蚀后Z的边界,(B)z表示的是B的平移,使其中心点位于z位置。

腐蚀与膨胀可以看作一种互逆运算,膨胀对原始区域扩大,腐蚀对原始区域缩小。在OpenCV中如何操作?erode()腐蚀,dilate()膨胀

void erode(
			InputArray src,
    		OutputArray dst,
    		InputArray kernel,
    		Point anchor=Point(-1,-1),
    		int iterations=1,
    		int borderType=BORDER_CONSTANT,
    		const Scalar& borderValue=morphologyDefaultB-orderValue()
)
    
void dilate(
			InputArray src,
    		OutputArray dst,
    		InputArray kernel,
    		Point anchor = Point(-1,-1),
    		int iterations = 1,
   			int borderType=BORDER_CONSTANT,
    		const Scalar& borderValue = morphologyDefault-BorderValue()
)

函数解释:

Erode()使用一个特定的结构化元素侵蚀一个图像,Dilate()使用图像结构化元素进行膨胀。

  • src:输入图像(二值化、灰度图像)

  • dst:输出图像(参数类型与输入图像一致)

  • kernel:表示定义的结构元素大小

  • anchor:结构元素的中心,如果默认参数(-1,-1),程序会自动将其设置为结构元素的中心。

  • iteration:迭代次数,可以选择对图像进行多次形态学运算。

  • borderType以及borderValue:可选择参数设置,针对边界设置。

那么腐蚀膨胀后的图像到底是什么样子?我们以下面这张图片做实验:

 使用腐蚀:

 使用膨胀:

 代码:

int main()
{
	cv::Mat srcImage = cv::imread("./image/CC.png");
	if (!srcImage.data)
		return 1;
	cv::Mat srcGray;
	cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	cv::Mat segDst, dilDst, eroDst;
	// 分通道二值化
	cv::inRange(srcGray, cv::Scalar(100),
		cv::Scalar(255), segDst);
	// 定义结构元素
	cv::Mat element = cv::getStructuringElement(
		cv::MORPH_ELLIPSE, cv::Size(5, 5));
	// 腐蚀膨胀操作
	cv::dilate(segDst, dilDst, element);
	cv::erode(segDst, eroDst, element);
	cv::imshow(" srcGray ", srcGray);
	cv::imshow(" segDst ", segDst);
	cv::imshow(" dilDst ", dilDst);
	cv::imshow(" eroDst ", eroDst);
	cv::waitKey();
	return 0;
}

cv::inRange:将两个阈值内的像素值设置为白色,而不在阈值内的像素值设置为黑色。

cv::getStructuringElement:可用于构造一个特定大小和形状的结构元素,用于图像形态学处理。

cv::getStructuringElement(
			int shape,		//结构元素形状
			Size ksize,		//结构元素大小
			Point anchor = Point(-1,-1)		//锚点 默认值Point(-1,-1)表示锚点位于结构元素中心
)

对于结构元素的形状:

从上往下是:方形、交错形状、椭圆形元素,内切于Rect(0, 0, esize.width, 0.esize.height)定义的矩形。

那么再往下讲叭:

膨胀操作中队输入图像进行特定结构元素操作取决于滑动窗口图像相关邻域的最大值,同时该函数可以多次队原输入图像进行操作。对于多通道图像,应该对每个通道独立地进行膨胀操作,再合并相应的结果。滑动窗口邻域最大值操作如下所示:

 腐蚀操作中对输入图像进行特定结构元素操作取决于滑动窗口图像相关邻域的最小值,同膨胀操作一样,也支持多次操作。对多通道数据进行分离处理,滑动窗口邻域最小值的操作如下:

形态学操作通过结构元素及邻域对图像的每个像素进行逻辑变换,经过形态学得到的新图像对应坐标(x',y')是通过对输入图像(x,y)及其邻域根据结构元素形状进行遍历,最终得到像素坐标变换后的图像。

0x02 开闭运算操作

形态学开运算操作能够去除噪声以及平滑目标等功能,其定义为:

 

这个操作再结合上面的公式来看,这个定义是在于先对图像进行腐蚀操作,然后再进行膨胀操作,结构元素各向同性的开运算操作主要用于消除图像中小于结构元素的细节部分,物体的局部形状不变。物体较背景明亮时能够排除小区域物体,消除高于邻近点的孤立点,达到去噪的作用,可以平滑物体轮廓,断开较窄的狭颈。

形态学闭运算操作能够填充目标区域内的离散小空间和分散部分,其定义为:

这个操作是用结构元素B对A先进行膨胀,然后再进行腐蚀。形态学闭操作能够排除小型黑洞(黑色区域),消除低于邻近点的孤立点,达到去噪的作用,也可以平滑物体轮廓,拟合较窄的间断和细长的沟壑,消除小孔洞,填补轮廓线中的断裂。

OpenCV中提供了morphologyEx函数用于形态学开闭运算操作,比如上一章讲的车牌检测就是运用了这个函数:

cv::void morphologyEx(
    		InputArray src,
    		OutputArray dst,
    		int op,
			InputArray kernel,
			Point anchor = Point(-1,-1),
			int iterations = 1,
			int borderType = BORDER_CONSTANT,
			const Scalar&borderValue = morph - ologyDefaultBorderValue()
)

计算复杂的形态学变换:

  • src:输入图像,二值图像或灰度图像。

  • dst:输出图像,参数类型于输入图像一致。

  • op:形态学操作算子类型,可以设置为MORPH_OPEN开操作、MORPH_CLOSE闭操作、MORPH_GRADIENT形态学梯度操作、MORPH_TOPHAT顶帽操作、MORPH_BLACKHAT黑帽操作。

  • iterations:设置腐蚀与膨胀操作的次数。

  • borderType与borderValue可选参数设置:针对边界处理。

那么这么操作下来上面那副图像会变成什么:

int main()
{
	cv::Mat srcImage = cv::imread("./image/CC.png");
	if (!srcImage.data)
		return 1;
	cv::Mat srcGray;
	cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	cv::Mat segDst, dilDst, eroDst;
	// 分通道二值化
	cv::inRange(srcGray, cv::Scalar(100),
		cv::Scalar(255), segDst);
	// 定义结构元素
	cv::Mat element = cv::getStructuringElement(
		cv::MORPH_ELLIPSE, cv::Size(10, 10));
	// 腐蚀膨胀操作
	cv::dilate(segDst, dilDst, element);
	cv::erode(segDst, eroDst, element);
	// 形态学闭操作
	cv::Mat closeMat;
	cv::morphologyEx(segDst,closeMat,cv::MORPH_CLOSE,element);
	// 形态学开操作
	cv::Mat openMat;
	cv::morphologyEx(segDst, openMat,cv::MORPH_OPEN,element);
	cv::imshow(" srcGray ", srcGray);
	cv::imshow(" segDst ", segDst);
	cv::imshow(" dilDst ", dilDst);
	cv::imshow(" eroDst ", eroDst);
	cv::imshow(" closeMat", closeMat);
	cv::imshow(" openMat", openMat);
	cv::waitKey();
	return 0;
}

 

 当我们把形态学那个元素长与宽都加大5的时候,可以发现断裂处的地方通过膨胀后被放大了,而细小的地方则被忽略:

 

0x03 形态学梯度

梯度用于刻画目标边界或边缘位于图像灰度级剧烈变换的区域,形态学梯度根据膨胀或腐蚀与原图作差组合来实现增强结构元素邻域中像素的强度,突出高亮区域的外围。最常用的形态学梯度是计算膨胀与腐蚀间的算术差,另外两种计算形态学梯度分别是膨胀结果与原图的算术差以及原图与腐蚀结果的算术差。对于结构元素E,形态学梯度G操作可以表示为:

 形态学操作的输出图像像素值是在对应结构元素而非局部过渡区域所定义的邻域中灰度级强度变化的最大值。形态学梯度操作如下:

// 形态学梯度
cv::Mat gradMat;
cv::morphologyEx(segDst, gradMat, cv::MORPH_GRADIENT, element);

最后生成的图像如下:

0x04 形态学Top-Hat

形态学Top-Hat变换是指形态学顶帽操作与黑帽操作,前者是计算源图像与开运算结果图之差,后者是计算闭运算结果与源图像之差

形态学Top-Hat变换是常用的一种滤波手段,具有高通滤波的某部分特性,可实现在图像中检测出周围背景亮结构周边背景暗结构

顶帽操作常用于检测图像中的结构。

黑帽操作常用于检测图像中的波谷结构。

那么拿下面这张图做对比:

 使用如下代码处理:

int main()
{
	cv::Mat srcImage = cv::imread("./image/beauty.png");
	if (!srcImage.data)
		return 1;
	cv::Mat srcGray;
	cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	
	// 定义结构元素
	cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(15,15));
	cv::Mat topHatMat, blackHatMat;
	// 形态学Top-Hat 顶帽
	cv::morphologyEx(srcGray, topHatMat,cv::MORPH_TOPHAT,element);
	// 形态学Top-Hat 黑帽
	cv::morphologyEx(srcGray, blackHatMat,cv::MORPH_BLACKHAT,element);

	cv::imshow("topHatMat", topHatMat);
	cv::imshow("blackHatMat", blackHatMat);

	cv::waitKey(0);

	return 0;
}

运行结果如下:

顶帽处理:

黑帽处理:

如果最后生成的图像的区分度太低的话,可以试着调整一下定义结构元素的大小,将其变大。

0x05 用在哪?角点提取、车牌提取

(一)形态学滤波角点提取

形态学边缘检测的原理是利用膨胀与腐蚀变化区特征来完成边缘检测,膨胀操作是将目标物体向周围邻域扩展,而腐蚀操作是将目标物体向邻域收缩。按照这么讲找边缘就很好找了。其实边缘恰好反映在了形态学腐蚀与膨胀中变化的区域。那么我们只需要将图像膨胀后以及腐蚀后进行作差运算,则可以得到物体的边缘。形态学的边缘检测可以利用形态学梯度操作函数morphologyEx直接得到,具体是通过计算形态学膨胀结果图与腐蚀结果图之差,再进行相应阈值化操作实现的。

由于结构元素有多种形状,因此我们会考虑对图像进行形态学滤波时,可进行相应结构元素的组合进而实现角点提取,opencv也提供了专门的函数去实现复杂的形状元素构建:

cv::Mat getStructingElement(
				int shape,		//形状元素
				Size ksize,		//大小
				Point anchor=Point(-1,-1))

这个函数在上面详述了,这就不继续往下讲了。

那么原理是什么:(摘自《OpenCV图像处理编程实例》)

形态学滤波在进行角点检测时闭边缘检测过程略显复杂,但基本原理相似。根据角点特征性质,对原图像先利用十字形的结构元素进行膨胀,这种情况下只会使目标物体在边缘处扩展,而角点并不会发生变化。然后利用菱形的结构元素对上一步得到的图形进行腐蚀操作,这种情况下会使目标物体在边缘处无变化,而在角点处发生收缩。接着使用X形结构元素对源图像进行膨胀操作,这种情况下会使得角点处发生扩展。最后利用矩形结构元素对上一步得到的图像进行腐蚀操作,这种情况会使角点恢复原状,同样边缘将腐蚀得更多。将得到的膨胀图与腐蚀图进行相减操作后,就能得到图像的角点。

那么接下来实现角点检测:

对于要跑的赛道 ,我觉得这个可以发挥很大的作用,它可以找到赛道的拐点,之后对赛道进行数学函数的处理。

#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include "opencv2/core/core.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/opencv.hpp>

using namespace cv;
int main()
{
	cv::Mat srcImage = cv::imread("./image/circle.jpg");
	if (!srcImage.data)
		return 1;
	cv::Mat srcGray;
	cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	// 定义结构元素
	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 result;
	dilate(srcGray, result, CrossMat);
	// 第2步:菱形对上步进行腐蚀
	erode(result, result, diamondMat);
	Mat result2;
	// 第3步:X形对原图进行腐蚀
	dilate(srcGray, result2, x);
	// 第4步:正方形对上步进行腐蚀
	erode(result2, result2, squareMat);
	// 第4步:计算差值
	absdiff(result2, result, result);
	threshold(result, result, 40, 255, THRESH_BINARY);
	// 绘图
	for (int i = 0; i < result.rows; i++)
	{
		// 获取行指针
		const uchar* data = result.ptr<uchar>(i);
		for (int j = 0; j < result.cols; j++)
		{
			// 如果是角点 则进行绘制圆圈
			if (data[j])
				circle(srcImage, Point(j, i), 8,
					Scalar(0, 255, 0));
		}
	}
	cv::imshow("result", result);
	cv::imshow("srcImage", srcImage);
	cv::waitKey(0);
	return 0;
}

(二)车牌目标提取

上篇博客:OpenCV入门(七)——车牌区域检测_郑烯烃快去学习的博客-程序员宅基地

根据形态学梯度检测原理,可应用垂直分量对车牌区域进行竖直边缘检测,然后利用形态学的水平与竖直方向的闭操作算子得到的竖直边缘进行区域连通得到图像Ic。

我们可以根据检测后车辆的目标尺寸大小,自适应改变闭操作算子矩阵,规则如下:

(1)检测目标尺寸宽度与高度为400-600的像素使用的闭操作单元算子分别为1*25矩阵与8 * 1矩阵。

(2)检测目标尺寸宽度与高度为200-300的像素使用的闭操作单元算子分别为1 * 20矩阵与6 * 1矩阵。

(3)检测目标尺寸宽度与高度大于600的像素使用的闭操作单元算子分别为 1 * 28矩阵与6 * 1矩阵。

(4)其余情况使用闭操作单元算子分别为1 *15矩阵与4 *1矩阵。

接着对Ic求其连通域,并在此基础上求最小外接矩形以得到图像Ir。然后对Ir中得到的最小的外接矩形进行车牌尺寸形态判定操作,去除大小、比例不符合要求的最小外接矩形,最终保留的外接矩形连通区域即为疑似车牌区域。

代码:

#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include "opencv2/core/core.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/opencv.hpp>

using namespace cv;
cv::Mat getPlate(int width, int height, cv::Mat srcGray)
{
	cv::Mat result;
	// 形态学梯度检测边缘
	morphologyEx(srcGray, result, MORPH_GRADIENT,
		Mat(1, 2, CV_8U, Scalar(1)));
	cv::imshow("1result", result);
	// 阈值化
	threshold(result, result, 255 * (0.1), 255,
		THRESH_BINARY);
	cv::imshow("2result", result);
	// 水平方向闭运算
	if (width >= 400 && width < 600)
		morphologyEx(result, result, MORPH_CLOSE,
			Mat(1, 25, CV_8U, Scalar(1)));
	else if (width >= 200 && width < 300)
		morphologyEx(result, result, MORPH_CLOSE,
			Mat(1, 20, CV_8U, Scalar(1)));
	else if (width >= 600)
		morphologyEx(result, result, MORPH_CLOSE,
			Mat(1, 28, CV_8U, Scalar(1)));
	else
		morphologyEx(result, result, MORPH_CLOSE,
			Mat(1, 15, CV_8U, Scalar(1)));
	// 垂直方向闭运算
	if (height >= 400 && height < 600)
		morphologyEx(result, result, MORPH_CLOSE,
			Mat(8, 1, CV_8U, Scalar(1)));
	else if (height >= 200 && height < 300)
		morphologyEx(result, result, MORPH_CLOSE,
			Mat(6, 1, CV_8U, Scalar(1)));
	else if (height >= 600)
		morphologyEx(result, result, MORPH_CLOSE,
			Mat(10, 1, CV_8U, Scalar(1)));
	else
		morphologyEx(result, result, MORPH_CLOSE,
			Mat(4, 1, CV_8U, Scalar(1)));
	return result;
}
int main()
{
	cv::Mat srcImage = cv::imread("./image/cheche.png");
	if (!srcImage.data)
		return 1;
	cv::Mat srcGray;
	cv::cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	cv::imshow("srcGray", srcGray);
	cv::Mat result = getPlate(400, 300, srcGray);
	// 连通域检测
	std::vector<std::vector<cv::Point> > blue_contours;
	std::vector<cv::Rect> blue_rect;
    //寻找外轮廓
	cv::findContours(result.clone(),
		blue_contours, CV_RETR_EXTERNAL,
		CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
	// 连通域遍历 车牌目标提取
	for (size_t i = 0; i != blue_contours.size(); ++i)
	{
		cv::Rect rect = cv::boundingRect(blue_contours[i]);
		double wh_ratio = double(rect.width) / rect.height;
		int sub = cv::countNonZero(result(rect));
		double ratio = double(sub) / rect.area();
		if (wh_ratio > 2 && wh_ratio < 8 && rect.height >
			12 && rect.width > 60 && ratio > 0.4)
		{
			cv::imshow("rect", srcGray(rect));
			cv::waitKey(0);
		}
	}
	cv::imshow("result", result);
	cv::waitKey(0);
	return 0;
}

效果:

关于连通区域检测函数:

OpenCV通过使用findContours函数,简单几个步骤就可以检测出物体的轮廓。

函数原型:

cv::findContours( InputOutputArray image, 
			  	  OutputArrayOfArrays contours,
             	  OutputArray hierarchy, 
                  int mode,
                  int method, 
                  Point offset=Point());
  • image:单通道图像矩阵,可以是灰度图,但更常用的是二值化图像,一般是结果canny、拉普拉斯等边缘检测算子处理过的二值化图像。

  • contours:定义为std::vector< std::vector< cv::Point> > blue_contours,是一个向量,并且是一个双重向量,向量每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少个元素。

  • hierarchy:定义为vector< Vec4i> hierarchy,这个定义为向量内每一个元素包含了4个int类型变量。所以它其实也是一个向量,向量内每个元素保存了一个包含4个int整形的数组。向量hierarchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。那么它的每一个元素的4个int类型变量分别为:hierarchy[i] [0]~hierarchy[i] [3],他分别表示的是第i轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果当前轮廓没有对应的后一个其中一个值的话,那么默认值就是-1。

  • mode:定义轮廓的检索模式:

    (1)CV_RETR_EXTERNAL,只检测最外围轮廓,包含在外围轮廓内的内围轮廓都将被忽略。

    (2)CV_RETR_LIST:检测所有轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、4个分量都会被置为-1。

    (3)CV_RETR_CCOMP :检测所有轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内所有的轮廓均属于顶层。

    (4)CV_RETR_TREE:检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。

  • method:定义轮廓的近似方法。

    (1)CV_CHAIN_APPROX_NONE :保存物体边界上所有连续的轮廓点到contours向量内。

    (2)CV_CHAIN_APPROX_SIMPLE :仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线上的信息不予保留。

    (3)CV_CHAIN_APPROX_TC89_L1CV_CHAIN_APPROX_TC89_KCOS:使用teth-Chinl chain近似算法。

  • Point:偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,也可以为负值。

在上面的程序中,使用形态学梯度边缘检测得到的图像,目的是为了突显边缘:

这个时候除了车牌以及其他一些明显特征被留下来了,但是其他干扰元素已经大部分的被滤掉了,确实这个操作可以滤掉很多干扰的地方。

之后他再进行阈值化,图像的变化是这样的:

也就是将我们显示出来的主体再明显化,但是同时也将其他干扰的地方阈值化出来了。

之后要自适应出自己的闭操作算子矩阵,对我们这幅图像进行闭操作,滤掉一些没有用的东西,最后得到的图像如下:

之后对齐检测连通区域。之后再剔除大小不合适的连通区域,最后得到车牌的区域。

到此为止,应该对这个操作有些深刻的理解以及见解了,那么这个方法跟我昨天使用的方法有什么区别?昨天的处理是在RGB图像转HSI图像的基础上进行,当我们使用形态学的时候,我们可以直接操作灰度图像,并且不需要指定车牌的背景颜色,可以适应更多种情况,因为使用形态学检测,可以快速的找到车牌的边缘。上一篇的找边缘是通过sobel算子进行卷积计算的,最后画出蓝色对应的区域的轮廓。之后再进行形态学闭操作,这一步是相同的,只不过现在多了个自适应大小,那么这个形态学闭操作,是将空间内间隙小的地方进行填充,也就是尽量的找到车牌的区域,防止遗漏,所以最后输出的车牌的区域,才会这么的花,但是没关系,他已经找到车牌了。最后的处理就是一样的了,找到符合的条件区域,大小等,将我们的车牌进行打印出来。


那么以上就是我对形态学技术的一些理解以及应用,这个技术确实可以造福很多种图像边缘提取、区域提取等多个领域,是个很好用的东西。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Alkaid2000/article/details/124598912

智能推荐

SWUSTOJ #961 进制转换问题_oj swust961-程序员宅基地

文章浏览阅读3.3k次,点赞4次,收藏11次。建立顺序栈或链栈,编写程序实现十进制数到二进制数的转换。_oj swust961

c语言库函数和头文件以及gcc下编译工程_gcc支持的c语言的图形库函数-程序员宅基地

文章浏览阅读1.8k次。一:windows下.dll动态链接库(相当于linux下的.a和.so文件,是源文件经过编译后的文件)在Windows世界中,有无数块活动的大陆,它们都有一个共同的名字——动态链接库。现在就走进这些神奇的活动大陆,找出它们隐藏已久的秘密吧!   初窥门径:Windows的基石  随便打开一个系统目录,一眼望去就能看到很多扩展名DLL的文件,这些就是经常说的“动态链接库”,DL_gcc支持的c语言的图形库函数

ESP8266 RTOS 下 Openssl 证书及使用和 Fragment 介绍_esp-idf 使用openssl-程序员宅基地

文章浏览阅读3k次。1 简介本文主要介绍基于 ESP8266_RTOS_SDK 的 SSL 加密使用方法,将分别介绍 ESP8266 作为 SSL client 和 SSL server 的使用方法。SSL 功能需要占用大量内存,请开发者在上层应用程序确保内存足够。在将 SSL fragment 设置为 8KB 以及证书用 private key RSA2048 的情况下, SSL 双向认证功能需要 30KB..._esp-idf 使用openssl

一文学会Spring,Spring最简单的入门教程(万字好文)_spring入门-程序员宅基地

文章浏览阅读8.3k次,点赞41次,收藏175次。spring教程_spring入门

统计学怎么求加权指数_暨南大学《统计学》中文习题 第十二章 统计指数-程序员宅基地

文章浏览阅读2.3k次。第十二章 统计指数(一)判断题1、 狭义指数是指反映社会经济现象变动与差异程度的相对数。( )是: 否:2、 广义上说,任何两个不同时间的同类指标的对比都可称为指数。( )是: 否:3、 在平均指标变动因素分析中,可变构成指数是专门用以反映总体构成变化影响的指数。( )是: 否:4、 在平均指标变动因素分析中,可变构成指数是专门用以反映总体构成变化影响的指数。 ( )是: 否:5、平均指标..._固定权数加权算术平均指数公式

浅谈接口加密_接口的数据交换是否有加密也应进行描述。-程序员宅基地

文章浏览阅读2.5k次。客户端在跟服务端进行数据交互的时候,比如登录操作,打开调试窗口,我们在输入用户名密码之后点击登录,可以看到我们调用的登录接口,在接口的返回数据中,可以看到登录的用户名和密码是明文显示,说明该接口没有进行加密操作。如果一个登录接口是加密的,那么我们在接口工具中直接使用用户名、密码登录是无法登录成功的,即使你的用户名密码正确,他还是会提示你的用户名密码不正确。......_接口的数据交换是否有加密也应进行描述。

随便推点

SSM学习笔记(三)——excel导入导出_ssm项目上传excle文件jsp-程序员宅基地

文章浏览阅读2k次。非专门的软件企业用的比较多的便是excel的导入和导出的功能,本次对近日实现的导入和导出功能做下记录。首先是工具类,这里参考了CSDN “如果海豚会飞” 的文章,但由于其文章内容有些残缺,且与QB个人开发的现状不符合,所以做了一定的修改。参考的文章:http://blog.csdn.net/hsf15768615284/article/details/73136029注意的导包:po_ssm项目上传excle文件jsp

Nat. Mach. Intell.| 机器学习显著降低药物组合筛选成本_synergyfinder协同作用怎么看?-程序员宅基地

文章浏览阅读2.3k次。作者 | 孙科研究方向 | 药物组合单位 | 湖南大学今天给大家介绍2019年12月发表在Nature Machine Intelligence的论文“Prediction of dru..._synergyfinder协同作用怎么看?

fastboot烧写hi3531_等待boot启动超时,单板上无fastboot或当前fastboot无法启动。 请检查当前progr-程序员宅基地

文章浏览阅读3.1k次。Boot Downloading started.Boot 100 % Downloaded.Boot Downloading completed!U-Boot 2010.06 (Jan 04 2014 - 09:35:56)DRAM: 256 MiBNAND: Special Nand id table Version 1.35Nand ID: 0x2C 0x88_等待boot启动超时,单板上无fastboot或当前fastboot无法启动。 请检查当前programm

ajax如何处理服务器返回的3种数据格式_ajxa返回的data-程序员宅基地

文章浏览阅读1.3w次,点赞2次,收藏22次。这篇东西本身是非常基础的,但是经常在处理ajax返回值的时候还是会犯各种小错误,所以就想到把这些方法结合以前写过的代码片段整理一下,方便以后复习和使用。另外呢,虽然能搜到很多类似的文档,但是整理一份自己的还是坠吼的= ̄ω ̄=ajax方法的参数常用的ajax参数比如url,data,type,包括预期返回类型dataType,发送到服务器的数据的编码类型contentTy_ajxa返回的data

栈溢出漏洞及栈溢出攻击_栈溢出漏洞产生的一些事件-程序员宅基地

文章浏览阅读1.2w次。1. 栈溢出的原因栈溢出(stack-based buffer overflows)算是安全界常见的漏洞。一方面因为程序员的疏忽,使用了 strcpy、sprintf 等不安全的函数,增加了栈溢出漏洞的可能。另一方面,因为栈上保存了函数的返回地址等信息,因此如果攻击者能任意覆盖栈上的数据,通常情况下就意味着他能修改程序的执行流程,从而造成更大的破坏。这种攻击方法就是栈溢出攻击(stack_栈溢出漏洞产生的一些事件

Express_前端express-程序员宅基地

文章浏览阅读1.7k次,点赞4次,收藏17次。初识 Express1.1 Express 简介1. 什么是 Express官方给出的概念:Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。 通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的。 Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。Express 的中文官网: http://www.expressjs.com.cn/2. 进一步_前端express