在ARM-Linux下实现车牌识别(一)------车牌提取_arm图像识别程序-程序员宅基地

技术标签: 车牌识别  opencv  车牌定位  Linux应用  

你好!这里是风筝的博客,

欢迎和我一起交流。


最近在弄车牌识别这个项目,对于机器视觉有些了解的人都知道,这个东西算是比较成熟了,在书里也有代码。
网上能找到的资料也比较多,所及借着这个机会在ARM开发板上实现以下车牌识别。
反正对于神经网络这些什么的我是不知道了,所以代码也是网上借鉴了的,我稍微整理注释了下。
先放下移植opencv的步骤:移植opencv到嵌入式arm详细过程
第一步做的就是车牌提取:
opencv
代码如下:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <string>
#include <cvaux.h> 
#include <stdio.h> 
#include <opencv2/gpu/gpu.hpp>
#include <opencv2/ml/ml.hpp>

using namespace cv; 
using namespace std;

//车牌宽高比为520/110=4.727272左右,误差不超过40%
//车牌高度范围在15~125之间,视摄像头距离而定(图像大小)
bool verifySizes_closeImg(const RotatedRect & candidate)
{
	float error = 0.4;//误差40%
	const float aspect = 4.7272;//44/14; //长宽比
	int min = 15*aspect*15;//20*aspect*20; //面积下限,最小区域
	int max = 125*aspect*125;//180*aspect*180;  //面积上限,最大区域
	float rmin = aspect - aspect*error; //考虑误差后的最小长宽比
	float rmax = aspect + aspect*error; //考虑误差后的最大长宽比

	int area = candidate.size.height * candidate.size.width;//计算面积
	float r = (float)candidate.size.width/(float)candidate.size.height;//计算宽高比
	if(r <1)
		r = 1/r;

	if( (area < min || area > max) || (r< rmin || r > rmax)  )//满足条件才认为是车牌候选区域
		return false;
	else
		return true;
}

void RgbConvToGray(const Mat& inputImage,Mat & outpuImage)  //g = 0.3R+0.59G+0.11B
{
	outpuImage = Mat(inputImage.rows ,inputImage.cols ,CV_8UC1);  

	for (int i = 0 ;i<inputImage.rows ;++ i)
	{
		uchar *ptrGray = outpuImage.ptr<uchar>(i); 
		const Vec3b * ptrRgb = inputImage.ptr<Vec3b>(i);
		for (int j = 0 ;j<inputImage.cols ;++ j)
		{
			ptrGray[j] = 0.3*ptrRgb[j][2]+0.59*ptrRgb[j][1]+0.11*ptrRgb[j][0];	
		}
	}
}

void normal_area(Mat &intputImg, vector<RotatedRect> &rects_optimal, vector <Mat>& output_area )
{
	float r,angle;
	for (int i = 0 ;i< rects_optimal.size() ; ++i)
	{
		//旋转区域
		angle = rects_optimal[i].angle;
		r = (float)rects_optimal[i].size.width / (float) (float)rects_optimal[i].size.height;
		if(r<1)
			angle = 90 + angle;//旋转图像使其得到长大于高度图像。
		Mat rotmat = getRotationMatrix2D(rects_optimal[i].center , angle,1);//获得变形矩阵对象
		Mat img_rotated;
		warpAffine(intputImg ,img_rotated,rotmat, intputImg.size(),CV_INTER_CUBIC);
		imwrite("car_rotated.jpg",img_rotated);//得到旋转图像

		//裁剪图像
		Size rect_size = rects_optimal[i].size;
		if(r<1)
			swap(rect_size.width, rect_size.height); //交换高和宽
		Mat  img_crop;
		getRectSubPix(img_rotated ,rect_size,rects_optimal[i].center , img_crop );//图像切割

		//用光照直方图调整所有裁剪得到的图像,使具有相同宽度和高度,适用于训练和分类
		Mat resultResized;
		//别人写的:
		/*resultResized.create(33,144,CV32FC1);
		resize(img_crop , resultResized,resultResized.size() , 0,0,INTER_CUBIC);
		resultResized.convertTo(resultResized, CV32FC1);
		resultResized = resultResized.reshape(1,1);*/
		
		resultResized.create(33,144,CV_8UC3);//CV32FC1????
		resize(img_crop , resultResized,resultResized.size() , 0,0,INTER_CUBIC);
		Mat grayResult;
		RgbConvToGray(resultResized ,grayResult);
		//blur(grayResult ,grayResult,Size(3,3));
		equalizeHist(grayResult,grayResult);

		output_area.push_back(grayResult);
	}
}


int main(int argc, char* argv[])
{
	Mat img_input= imread("./car.jpg");//加载图片
	if(img_input.empty())//如果读入图像失败
	{
		cout << "Can not load image" << endl;
		return -1;
	}

	Mat hsvImg ;
	cvtColor(img_input,hsvImg,CV_BGR2HSV);//RGB模型转换成HSV模型
	imwrite("car_hsv.jpg",hsvImg);//看下hsv效果

	vector <Mat> hsvSplit;
    split(hsvImg,hsvSplit);//将图像的各个通道分离
	equalizeHist(hsvSplit[2],hsvSplit[2]);//直方图均衡化,提高图像的质量
	merge(hsvSplit,hsvImg);//将分离的多个单通道合成一幅多通道图像
	imwrite("car_hsv1.jpg",hsvImg);//看下处理效果

	const int min_blue =100;//最小蓝车区域
	const int max_blue =240;//最大蓝车区域
	int avg_h = (min_blue+max_blue)/2;
	int channels = hsvImg.channels();
	int nRows = hsvImg.rows;
	//图像数据列需要考虑通道数的影响;
	int nCols = hsvImg.cols * channels;

	if (hsvImg.isContinuous())//连续存储的数据,按一行处理
	{
		nCols *= nRows;
		nRows = 1;
	}

	int i, j;
	unsigned char* p;
	const float  minref_sv = 64; //参考的S V的值
	const float max_sv = 255; // S V 的最大值

	for (i = 0; i < nRows; ++i)//根据蓝色在HSV在的区域每个通道的取值范围将此作为阈值,提取出图片中蓝色部分作为备选区域
	{
		p = hsvImg.ptr<uchar>(i);//有效提高了车牌和车色颜色在不相差较大的情况下的识别率
		for (j = 0; j < nCols; j += 3)//致命问题:蓝色的车和蓝色的牌照?
		{
			int H = int(p[j]); //0-180
			int S = int(p[j + 1]);  //0-255
			int V = int(p[j + 2]);  //0-255
			bool colorMatched = false;

			if (H > min_blue && H < max_blue)
			{
				int Hdiff = 0;
				float Hdiff_p = float(Hdiff) / 40;
				float min_sv = 0;

				if (H > avg_h)
				{
					Hdiff = H - avg_h;
				}	
				else
				{
					Hdiff = avg_h - H;
				}
				min_sv = minref_sv - minref_sv / 2 * (1 - Hdiff_p);
                if ((S > 70&& S < 255) &&(V > 70 && V < 255))
					colorMatched = true;
			}
			if (colorMatched == true) 
			{
				p[j] = 0; p[j + 1] = 0; p[j + 2] = 255;
			}
			else 
			{
				p[j] = 0; p[j + 1] = 0; p[j + 2] = 0;
			}
		}
	}

	Mat src_grey;
	Mat img_threshold;
	vector<Mat> hsvSplit_done;
	
	split(hsvImg, hsvSplit_done);
	src_grey = hsvSplit_done[2];//提取黑色分量
	imwrite("car_hsvSplit.jpg",src_grey);//查看分离通道出来的车牌
	vector <RotatedRect>  rects;
	Mat element = getStructuringElement(MORPH_RECT ,Size(17 ,3));  //闭形态学的结构元素
	morphologyEx(src_grey ,img_threshold,CV_MOP_CLOSE,element); //闭运算,先膨胀后腐蚀,连通近邻区域(填补白色区域的间隙)
	morphologyEx(img_threshold,img_threshold,MORPH_OPEN,element);//形态学处理
	imwrite("car_morphology.jpg",img_threshold);//查看threshold

	vector< vector <Point> > contours;//寻找车牌区域的轮廓
	findContours(img_threshold ,contours,CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//只检测外轮廓。存储所以轮廓点
	//绘制轮廓
	/*for(int find=0; find < contours.size(); find++)
		drawContours(img_threshold, contours, find, Scalar(255), 2);
	imwrite("car_contours.jpg",img_threshold);//查看轮廓*/

	//对候选的轮廓进行进一步筛选
	vector< vector <Point> > ::iterator itc = contours.begin();
	while( itc != contours.end())
	{
		RotatedRect mr = minAreaRect(Mat( *itc )); //返回每个轮廓的最小有界矩形区域
		if(!verifySizes_closeImg(mr))  //判断矩形轮廓是否符合要求
		{
			itc = contours.erase(itc);
		}
		else     
		{

			rects.push_back(mr);
			++itc;
		}      
	}
	vector <Mat> output_area;
	normal_area(img_input ,rects,output_area);  //获得144*33的候选车牌区域output_area
	imwrite("car_area.jpg",output_area[0]);//得到候选区域,这里可能会获得多个候选区域,最好使用svm训练一下

	return 0;
	
}

我们看下效果:
原图car.jpg,我在学校随便拍的一张图:
car

看下hsv的效果图car_hsv.jpg:
hsv

接着直方图均衡化后的图car_hsv1.jpg:
hsv1

提取蓝车区域后,分离出来,提取分量 car_hsvSplit.jpg:
split
可以看出,找到了车牌部分。我拍的这图质量还好,当车辆与车牌都是蓝色时就不好弄出这个了

接着看下形态学处理 car_morphology.jpg:
morphology
白色区域都被联通了。

然后看下旋转的图像 car_rotated.jpg:
rotated

最后,剪切出车牌即可 car_area.jpg:
area
这样就提取车了一张车牌。

因为这图比较理想,事实上,可能识别出多个蓝车区域多为车牌,也就是多个候选区域。这里我们图像理想,只识别出一个候选区域,所以我直接把候选区域0,即output_area[0]打印出来就是车牌了,事实上,我们应该加入SVM训练,作为最后判断是否是车牌的依据。

最后说一句,这程序只能识别蓝色的车牌,因为他是直接查找蓝色矩形区域的!
在ARM-Linux下实现车牌识别(二)------车牌识别

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

智能推荐

Unity关于不同脚本之间相互调用变量的方法_nodemcu如何调用不同lua脚本的变量-程序员宅基地

文章浏览阅读4.5w次,点赞40次,收藏111次。本来是想绕开一个脚本调用其他脚本变量这个事情的,但是不管怎么写最后还是绕回来了,有些功能必须这样才好完成。小伙伴们大同小异地提供了三种方法,但都是与加载组件有关系,比如这个:原文地址:https://blog.csdn.net/woerxi/article/details/17621841#commentBox但是我不喜欢,只是调用脚本的变量,还有把变量他妈和他奶奶一起带上,好麻烦……至少我这个功..._nodemcu如何调用不同lua脚本的变量

oracle library cache pin,解决library cache pin等待事件-程序员宅基地

文章浏览阅读811次。本人最后一个9i的客户前两天出了性能问题,一线生产数据库,十万火急,结果发现9i数据库的一个节点连接数为1300左右,尽然有240个异常等待事件,绝大部分是library cache pin和library cache lock,翻出05年写的一遍手记,感觉真的老,解决此问题,如下:造成数据库性能下降或挂起的原因很多,“library cache pin”等待是其中之一。当数据库性能严重下降或挂起..._oracle library cache pin

计算机的用户账户无法更改密码,Win7系统无法修改账号密码怎么办?电脑不能修改账号密码解决方法...-程序员宅基地

文章浏览阅读978次。我们在使用电脑的时候,设置电脑系统密码是为了不让他人使用自己的计算机,只有知道开机密码的用户才可以顺利进入系统桌面,但是如果你打算修改登录密码的时候,却发现系统无法修改密码。那么Win7系统无法修改账号密码怎么办?装机之家分享一下电脑不能修改账号密码解决方法。电脑不能修改账号密码解决方法1、首先我们点击桌面左下角“开始菜单”,再点击“控制面板”,在控制面板界面中将查看方式修改为“类别”,点击“系统..._win7创建密码提示不能更改

示例:WCF中Post、Get方式传递消息_wcf get-程序员宅基地

文章浏览阅读3.4k次,点赞4次,收藏8次。一、目的:搭建利用windows服务,控制台为宿主的Post、Get服务,应用Json或字符串的方式进行参数传递 二、服务端搭建 1、定义实体/// &lt;summary&gt; /// 消息实体 /// &lt;/summary&gt; public class MessageEntity { /// &lt;summary..._wcf get

RabbitMQ集群搭建--CentOs7、rabbitmq-server3.7_package: rabbitmq-server-3.7.7-1.el7.noarch (/rabb-程序员宅基地

文章浏览阅读511次。这篇文章只写命令,看不懂的可以参考上一篇文章,谢谢!https://blog.csdn.net/YHF886/article/details/88656239由于RabbitMQ3.7安装需要依赖erlang21版本,相对于RabbitMQ3.6集群比较麻烦df -hvim /etc/hostname #修改主机名vim /etc/hosts ..._package: rabbitmq-server-3.7.7-1.el7.noarch (/rabbitmq-server-3.7.7-1.el7.no

matlab机器人工具箱的使用(5)_six_dof.fkine-程序员宅基地

文章浏览阅读1.9k次。学习目标:matlab机器人工具箱的使用(5)学习内容:1、工作空间可视化学习记录:_six_dof.fkine

随便推点

Tomcat7.0无项目启动时候报错_stockticker', 'async.stockticker@380911e5-程序员宅基地

文章浏览阅读569次。二月 29, 2020 4:59:09 下午 org.apache.catalina.startup.VersionLoggerListener log信息: Server.服务器版本: Apache Tomcat/7.0.100二月 29, 2020 4:59:09 下午 org.apache.catalina.startup.VersionLoggerListener log信息..._stockticker', 'async.stockticker@380911e5

fzu_1608 Huge Mission线段树_区间修改求和_oaiei is busy working with his graduation design…-程序员宅基地

文章浏览阅读415次。Problem 1608 Huge MissionAccept: 405 Submit: 1025Time Limit: 1000 mSec Memory Limit : 32768 KB Problem DescriptionOaiei is busy working with his graduation design recently. If he_oaiei is busy working with his graduation design…

webpack实践--npm run dev 报错解决_webpack 2.9.6 npm run dev-程序员宅基地

文章浏览阅读1.6k次,点赞2次,收藏3次。我是按照资料书,实践webpack-dev-server的。按照步骤,安装npm install webpack-dev-server --save-dev【当然我已经安装过webpack,webpack-cli了】然后配置,package.json{...... "scripts": { "build": "webpack", "dev": "webpac..._webpack 2.9.6 npm run dev

怎样升级华为鸿蒙系统,怎么升级到鸿蒙2.0系统呀-程序员宅基地

文章浏览阅读305次。[分享交流]怎么升级到鸿蒙2.0系统呀136758电梯直达huafen009905565新学乍练发表于 2020-9-17 17:40:40来自:HUAWEI Mate 30 Pro 5G最新回复 2020-11-10 13:39:32BH8AVW花粉版主发表于 2020-9-17 17:41:50来自:HUAWEI Mate 30 Pro 5G请等待后续会推送哈!点评哈哈!不知道还要多久发表于..._华为鸿蒙系统升级勋章在哪里查

【一文讲通】BLDC的六步法&PMSM的FOC法综合_bldc和pmsm电机的驱动电路-程序员宅基地

文章浏览阅读9.1k次,点赞30次,收藏301次。查阅 n^2 的各方资料,对 BLDC&PMSM 进行一个大综合、大整理,查阅、比较和整理 大量的 网络教程、大厂手册、开源解决方案,在这里形成 原理和解决方案 的打通式介绍。_bldc和pmsm电机的驱动电路

腾讯直播——推流SDK(Android)_手机腾讯推流测试工具-程序员宅基地

文章浏览阅读1.4w次。https://www.qcloud.com/document/product/267/4735功能篇腾讯视频云RTMP SDK由两部分构成:推流器 + 播放器,本文将主要介绍推流器的相关信息。该SDK遵循标准RTMP视频推送协议,可以对接包括腾讯云在内的标准视频直播服务器。与此同时,SDK内部囊括了腾讯音视频团队多年的技术积累,在视频压缩、_手机腾讯推流测试工具

推荐文章

热门文章

相关标签