OpenCV 入门(三)—— 车牌筛选

news2024/11/22 18:25:48

OpenCV 入门系列:

OpenCV 入门(一)—— OpenCV 基础
OpenCV 入门(二)—— 车牌定位
OpenCV 入门(三)—— 车牌筛选
OpenCV 入门(四)—— 车牌号识别
OpenCV 入门(五)—— 人脸识别模型训练与 Windows 下的人脸识别
OpenCV 入门(六)—— Android 下的人脸识别
OpenCV 入门(七)—— 身份证识别

本篇文章要介绍如何对从候选车牌中选出最终进行字符识别的车牌。

无论是通过 Sobel 还是 HSV 计算出的候选车牌都可能不止一个,需要对它们进行评分,选出最终要进行识别的车牌。这个过程中会用到两个理论知识:支持向量机和 HOG 特征。

1、支持向量机

1.1 SVM 简介

支持向量机(Support Vector Machine,SVM)是一类按监督学习(Supervised Learning)方式对数据进行二元分类的广义线性分类器。用通俗的话来讲,就是用来分类,或者说挑选东西的。

对于车牌识别而言,车牌定位的候选车牌图可以分为两类:车牌与非车牌。SVM 可以对候选图进行测评,告诉我们图中的是不是车牌,相似程度是多少。

当然,SVM 可以进行分类的前提还是我们使用正负样本对其进行了训练。SVM 的训练数据既有特征又有标签,通过训练,让机器可以自己找到特征和标签之间的联系,在面对只有特征没有标签的数据时,可以判断出标签,这属于机器学习中的监督学习。

1.2 核函数

SVM 中有一个重要概念就是核函数。它的目标是找到一个能够将数据点分为不同类别的最优超平面(或者在非线性情况下是最优超曲面)。对于线性可分的情况,存在一个超平面可以完全将两个类别的数据分开。但是,在某些情况下,数据可能无法通过一个线性超平面进行完全分离,这就是线性不可分的情况。

SVM 线性可分:样本数据使用二维的线就可分类:

svm线性可分

SVM 线性不可分:左侧图片中的数据样本无法在二维平面内用线划分,称为线性不可分,只能像右侧图片那样用一个平面分开:

svm线性不可分

为了处理线性不可分的数据,引入了核函数的概念。核函数能够将输入数据从原始的特征空间(通常是低维空间)映射到一个更高维的特征空间,使得在新的特征空间中数据线性可分。这意味着在原始特征空间中无法线性分割的数据,在映射到高维特征空间后可以通过一个超平面进行线性分割。通常我们将这个过程称为提维,分离超平面就是通过提围计算出来的。

核函数的作用是在不显式计算映射到高维特征空间的情况下,直接在低维特征空间中进行计算。这样可以避免高维空间的计算复杂性,并且通过核函数的巧妙选择,可以实现高维特征空间的效果。

常见的核函数包括线性核函数、多项式核函数和径向基函数(Radial Basis Function,RBF)核函数。线性核函数对应于线性可分的情况,而多项式核函数和 RBF 核函数则可以处理线性不可分的情况。

1.3 SVM 训练流程

SVM 训练流程如下图:

svm训练流程

步骤:

  1. 预处理(原始数据 -> 学习数据(无标签)):预处理步骤主要处理的是原始数据到学习数据的转换过程(真正的车牌图片和不是车牌的图片)
  2. 打标签(学习数据(无标签)-> 学习数据(带标签)):将未贴标签的数据转化为贴过标签的学习数据
  3. 分组(学习数据(带标签)-> 分组数据):将数据分为训练集和测试集
  4. 训练(训练数据 -> 模型):加载待训练的车牌数据和非车牌数据,合并数据,配置 SVM 模型的训练参数进行训练

2、HOG 特征

HOG(Histogram of Oriented Gradient)特征是局部归一化的梯度方向直方图,是一种对图像局部重叠区域的密集型描述符,是用于目标检测和图像识别的特征描述方法,它通过计算局部区域的梯度方向直方图来构成特征。它在计算机视觉领域中广泛应用,特别是在行人检测等任务中取得了很好的效果。

HOG 特征的计算步骤如下:

  1. 图像预处理:将输入图像转换为灰度图像,去除颜色信息,以减少计算量。

  2. 梯度计算:计算图像中每个像素点的梯度信息。使用一阶导数(如 Sobel 算子)来计算水平和垂直方向上的梯度值,然后计算每个像素点的梯度幅值和梯度方向。

  3. 单元划分:将图像划分为小的连续区域,称为单元。通常使用 3 × 3 或 4 × 4 像素的单元。

  4. 梯度直方图统计:在每个单元中,对每个像素点的梯度方向进行统计。将梯度方向范围分成若干个区间(通常是 9 个),然后统计每个区间内的梯度幅值的累加和。这样就得到了一个梯度直方图。

  5. 块归一化:将相邻的若干个单元组成一个块,对每个块内的梯度直方图进行归一化处理。归一化可以降低光照变化对特征的影响,并增强特征的鲁棒性。

  6. 特征向量拼接:将所有块内的归一化梯度直方图按顺序拼接起来,形成最终的 HOG 特征向量。

HOG 特征的优点是能够捕捉图像中物体的边缘和纹理等局部特征,并且对光照变化相对鲁棒。它在行人检测等任务中被广泛使用,通常与支持向量机(SVM)等分类器结合使用,用于目标检测和图像识别。

3、代码实现

评分肯定是先通过正负样本学习,训练出一个特征集合,我们需要先加载这个 xml 文件:

int main() {
    // 加载车牌图片
	Mat src = imread("C:/Users/UserName/Desktop/Test/test5.jpg");
    // 新增加载特征集合
	LicensePlateRecognizer lpr("C:/Users/UserName/Desktop/Test/svm.xml");
    // 识别
	string str_plate = lpr.recognize(src);
	cout << "车牌号码:" << str_plate << endl;
	return 0;
}

在 LicensePlateRecognizer 进行识别时,需要调用评分的函数 predict():

/**
* 车牌识别 = 车牌定位 + 车牌检测 + 字符识别
*/
string LicensePlateRecognizer::recognize(Mat src)
{
	// 传入原图的克隆版本,以防在原图上的绘制影响后续算法定位
	Mat src_clone = src.clone();
	// 1.车牌定位,使用 Sobel 算法定位
	vector<Mat> sobel_plates;
	sobelLocator->locate(src_clone, sobel_plates);
	// 使用 HSV 算法定位
	src_clone = src.clone();
	vector<Mat> color_plates;
	colorLocator->locate(src_clone, color_plates);

	// 将两种车牌合并到一个集合中
	vector<Mat> plates;
	plates.insert(plates.end(), sobel_plates.begin(), sobel_plates.end());
	plates.insert(plates.end(), color_plates.begin(), color_plates.end());
	// 释放 sobel_plates 和 color_plates 内的 Mat
	for each (Mat m in sobel_plates)
	{
		m.release();
	}
	for each (Mat m in color_plates)
	{
		m.release();
	}

	// 2.精选车牌定位得到的候选车牌图
	char windowName[100];
	for (int i = 0; i < plates.size(); i++)
	{
		sprintf(windowName, "%zd 候选车牌", i);
		imshow(windowName, plates[i]);
		waitKey();
	}
	// 评分,将最接近车牌的图片保存到 plate 中,其索引保存在 index 中
	Mat plate;
	int index = svmPredictor->predict(plates, plate);

	src_clone.release();

    // 暂时还无法识别到车牌号,返回一个测试字符串
	return string("12345");
}

svmPredictor 就是通过 SVM 进行车牌评分的类,它需要创建一个 SVM 对象,还需要创建一个 HOGDescriptor:

#ifndef SVMPREDICTOR_H
#define SVMPREDICTOR_H

#include <opencv2/opencv.hpp>
#include <string>
// 机器学习 Machine Learning
#include <opencv2/ml.hpp>

using namespace std;
using namespace cv;
using namespace ml;

class SvmPredictor {
public:
	SvmPredictor(const char* svm_model);
	~SvmPredictor();

	virtual int predict(vector<Mat> candi_plates, Mat& dst_plates);
private:
    // 支持向量机对象
	Ptr<SVM> svm;
    // HOG 特征对象
	HOGDescriptor* svmHog = nullptr;

	void getHOGFeatures(HOGDescriptor* svmHog, Mat src, Mat& dst);
};

#endif // !SVMPREDICTOR_H

我们需要了解 HOGDescriptor 的创建参数:

SvmPredictor::SvmPredictor(const char* svm_model)
{
	svm = SVM::load(svm_model);
	svmHog = new HOGDescriptor(Size(128, 64), Size(16, 16), Size(8, 8), Size(8, 8), 3);
}

SvmPredictor::~SvmPredictor()
{
	if (svm)
	{
		svm->clear();
		svm.release();
	}
}

创建 HOGDescriptor 传了 4 个 Size 对象,它们的含义如下:

	/** @overload
    @param _winSize 使用给定的值设置窗口大小
    @param _blockSize 使用给定的值设置块大小
    @param _blockStride 使用给定的值设置滑动增量大小
    @param _cellSize 使用给定的值设置胞元(CellSize)大小
    @param _nbins 使用给定的值设置梯度方向
    */
    CV_WRAP HOGDescriptor(Size _winSize, Size _blockSize, Size _blockStride,
                  Size _cellSize, int _nbins, int _derivAperture=1, double _winSigma=-1,
                  HOGDescriptor::HistogramNormType _histogramNormType=HOGDescriptor::L2Hys,
                  double _L2HysThreshold=0.2, bool _gammaCorrection=false,
                  int _nlevels=HOGDescriptor::DEFAULT_NLEVELS, bool _signedGradient=false)

窗口大小设置为 (128, 64) ,作用是扫描图片中指定大小区域的像素,示意图如下:

窗口

一个窗口可以分成若干块,比如我们在代码中指定了块大小为 (16, 16),那么一个 (128, 64) 的窗口就可以在横向放 4 个块,纵向放 8 个块:

块和步

块滑动增量指定一个块在横纵方向上滑动步长为 (8, 8),胞元大小也指定为 (8, 8),那么一个 (16, 16) 的块中就包含 4 个胞元。最后的梯度方向 _nbins 指定为 3,在一个胞元内统计 3 个方向的梯度直方图,每个方向为 180 / 3 = 60°(将水平 180° 进行三等分)。

上面这个检测窗口可以被分为 ((128 - 16) / 8 + 1) * ((64 - 16) / 8 + 1) = 105 个块,一个块有 4 个胞元(Cell),一个胞元的 Hog 描述子向量的长度是 9。设置参数时必须要保证两个乘数内部是可以整除的。

统计梯度直方图特征,就是将梯度方向(0 ~ 360)划分为 x 个区间,将图像化为若干个 16 × 16 的窗口,每个窗口又划分为 x 个 block,每个 block 再化为 4 个 Cell(8 × 8)。对每一个 Cell,算出每一像素点的梯度方向,按梯度方向增加对应 bin 的值,最终综合 N 个 Cell 的梯度直方图组成特征。

简单来说,车牌的边缘与内部文字组成的一组信息(在边缘和角点的梯度值是很大的,边缘和角点包含了很多物体的形状信息),HOG 就是抽取这些信息组成一个直方图。

HOG:梯度方向弱化光照的影响,适合捕获轮廓

LBP:中心像素的 LBP 值反映了该像素周围区域的纹理信息

predict() 参考代码:

int SvmPredictor::predict(vector<Mat> candi_plates, Mat& dst_plate)
{
	Mat plate;
	float score;
	float minScore = FLT_MAX;
	int minIndex = -1;
	for (int i = 0; i < candi_plates.size(); i++)
	{
		plate = candi_plates[i];
		// 准备获取车牌图片的 HOG 特征,先获取灰度图
		Mat gray;
		cvtColor(plate, gray, COLOR_BGR2GRAY);

		// 二值化(非黑即白,对比更强烈)
		Mat shold;
		threshold(gray, shold, 0, 255, THRESH_OTSU + THRESH_BINARY);

		// 获取特征
		Mat feature;
		getHOGFeatures(svmHog, shold, feature);

		// 获取样本
		Mat sample = feature.reshape(1, 1);

		// 获取评分,评分越小越像目标
		score = svm->predict(sample, noArray(), StatModel::Flags::RAW_OUTPUT);
		printf("SVM候选车牌%d的评分是:%f\n", i, score);
		
		// 记录最小分数的索引
		if (score<minScore)
		{
			minScore = score;
			minIndex = i;
		}

		// 释放
		gray.release();
		shold.release();
		feature.release();
		sample.release();
	}

	// 找到了目标图片就把该图片复制给结果参数 dst_plate
	if (minIndex >= 0)
	{
		dst_plate = candi_plates[minIndex].clone();
		imshow("SVM 评测最终车牌", dst_plate);
		waitKey();
	}

	return minIndex;
}

获取特征其实就是通过 HOGDescriptor 计算出特征集合:

void SvmPredictor::getHOGFeatures(HOGDescriptor* svmHog, Mat src, Mat& dst)
{
	// 归一化处理
	Mat trainImg = Mat(svmHog->winSize, CV_32S);
	resize(src, trainImg, svmHog->winSize);

	// 计算特征
	vector<float> desc;
	svmHog->compute(trainImg, desc, svmHog->winSize);

	// 特征图拷贝给结果 dst
	Mat feature(desc);
	feature.copyTo(dst);

	// 释放
	feature.release();
	trainImg.release();
}

运行代码,可以看到有 4 个候选车牌,其中最后一个评分最低,是最符合标准的车牌:

2024-4-4.SVM评分选出最终图片

参考资料:

学习Opencv2.4.9(四)—SVM支持向量机

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1649695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

编译适配纯鸿蒙系统的ijkplayer中的ffmpeg库

目前bilibili官方的ijkplayer播放器&#xff0c;是只适配Android和IOS系统的。而华为接下来即将发布纯harmony系统&#xff0c;是否有基于harmony系统的ijkplayer可以使用呢&#xff1f; 鸿蒙版ijkplayer播放器是哪个&#xff0c;如何使用&#xff0c;这个问题&#xff0c;大家…

Linux 第二十二章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

基于FPGA的累加器及数码管显示VHDL代码Quartus仿真

名称&#xff1a;基于FPGA的累加器及数码管显示VHDL代码Quartus仿真&#xff08;文末获取&#xff09; 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 累加器及数码管显示 1、可以通过按键输入1~9 2、数字输入后进行累加&#xff0c;将累加结果显示…

Day 26 数据库日志管理

数据库日志管理 一&#xff1a;日志管理 1.日志分类 ​ 错误日志 &#xff1a;启动&#xff0c;停止&#xff0c;关闭失败报错。rpm安装日志位置 /var/log/mysqld.log ​ 通用查询日志&#xff1a;所有的查询都记下来 ​ 二进制日志&#xff1a;实现备份&#xff0c;增量备份…

rockchip sensors da215s适配

一 、 RK3568 da215s适配 ,增加一个新的 sensor 驱动需做一些适配工作。 SOC&#xff1a;RK3568 KERNEL&#xff1a;Android 12 二、 Android sensors 架构 三、 Sensors hal 与 kernel driver 的通信框图 四、 Rockchip sensors hal 介绍 代码路径&#xff1a; hardw…

RapidJSON介绍

1.简介 RapidJSON 是一个 C 的 JSON 解析库&#xff0c;由腾讯开源。 支持 SAX 和 DOM 风格的 API&#xff0c;并且可以解析、生成和查询 JSON 数据。RapidJSON 快。它的性能可与strlen() 相比。可支持 SSE2/SSE4.2 加速。RapidJSON 独立。它不依赖于 BOOST 等外部库。它甚至…

上位机图像处理和嵌入式模块部署(树莓派4b镜像烧录经验总结)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 陆陆续续也烧录了好多次树莓派的镜像了&#xff0c;这里面有的时候很快&#xff0c;有的时候很慢。特别是烧录慢的时候&#xff0c;也不知道是自己…

crossover怎么打开软件 mac怎么下载steam crossover下载的软件怎么运行

CrossOver是一款Mac和Linux平台上的类虚拟机软件&#xff0c;通过CrossOver可以运行Windows的可执行文件。如果你是Mac用户且需要使用CrossOver&#xff0c;但是不知道CrossOver怎么打开软件&#xff0c;如果你想在Mac电脑上玩Windows游戏&#xff0c;但不知道怎么下载Steam&am…

大模型改变了哪些工作方式?

大模型的崛起深刻改变了我们的工作方式。如今&#xff0c;许多行业已广泛应用大型机器学习模型&#xff0c;实现了自动化数据处理、智能决策和高效分析。这一变革不仅释放了大量人力资源&#xff0c;使得人们能够专注于更具创造性的任务&#xff0c;还大幅提升了工作效率和准确…

【mobx-入门与思考】

介绍 mobx 是 nodejs生态中的框架&#xff0c; 主要用于做状态管理&#xff0c;可以监控变量状态的变化。 nodejs中除了mobx&#xff0c;还有个redux&#xff0c;也是做状态管理的&#xff0c;都是比较成熟的框架&#xff0c;二者的选择可以参考 【nodejs状态管理: Redux VS M…

录屏软件哪个好用?这4款不容错过!

在现代社会中&#xff0c;信息的传递和分享变得越来越重要。一个好的录屏软件能够帮助我们将想要分享的信息快速直观地展示给他人。 通过下文推荐的4款录屏软件&#xff0c;我们可以轻松地分享自己的知识、经验和见解&#xff0c;让更多的人受益。 方法一&#xff1a;QQ软件进…

服务器2080ti驱动的卸载与安装

服务器2080ti驱动的卸载与安装 前言1、下载驱动2、驱动卸载与安装2.1 卸载原来驱动2.2 安装新驱动 3、查看安装情况 前言 安装transformers库&#xff0c;运行bert模型时出错&#xff0c;显示torch版本太低&#xff0c;要2.0以上的&#xff0c;所以更新显卡驱动&#xff0c;重…

基于FPGA的数字电子钟VHDL代码Quartus仿真

名称&#xff1a;基于FPGA的数字电子钟VHDL代码Quartus仿真&#xff08;文末获取&#xff09; 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 数字电子钟 1)设计一个能显示秒、分、时的24小时数字钟 2)用数码管显示出时&#xff0c;分&#xff0c;…

MFC列表控件用ADO添加数据实例

1、本程序基于前期我的博客文章《MFC用ADO连接ACESS数据库实例(免费源码下载)》 程序功能通过编辑框、组合框实时将数据写入ACESS数据库并在列表控件上显示。 2、在主界面资源视图上加上一个按钮控件、两个静态文本、一个编辑框IDC_EDIT1变量名name、一个组合框IDC_COMBO1变量名…

网络机顶盒哪个好?2024畅销网络机顶盒排行榜

因买网络机顶盒踩雷的人不在少数&#xff0c;许多不懂网络机顶盒哪个好的消费者在挑选时会参考网络机顶盒排行榜&#xff0c;这次小编带来了业内最新发布的热销网络机顶盒排行榜&#xff0c;想买网络机顶盒可以看看入围的以下品牌&#xff0c;是目前最受消费者欢迎的品牌。 一&…

参数配置不生效导致海思1151芯片TPC功率超大,引起性能恶化。

• 【Wi-Fi领域】【现网案例4】参数配置不生效导致海思1151芯片TPC功率超大&#xff0c;引起性能恶化。 【问题描述】XXX客户反馈OLT-HG8245W5-6T–Wi-Fi–WA8021V5-LAN-PC组网概率出现近距离测速只有20Mbps 【问题单】DTS2022101410914 【问题分析】 在客户反馈此问题后&#…

面试集中营—JVM篇

一、JVM内存模型 线程独占&#xff1a;栈&#xff0c;本地方法栈&#xff0c;程序计数器; 线程共享&#xff1a;堆&#xff0c;方法区 虚拟机栈&#xff1a;线程私有的&#xff0c;线程执行方法是会创建一个栈阵&#xff0c;用来存储局部变量表&#xff0c;操作栈&#xff0c;…

W801学习笔记二十二:英语背单词学习应用——下

续上篇&#xff1a; W801学习笔记二十一&#xff1a;英语背单词学习应用——上 五、处理用户交互 由于英语也是采用了和唐诗一样的《三分钟限时挑战》《五十题竞速挑战》《零错误闯关挑战》&#xff0c;所以用户交互的逻辑和唐诗是一样的。所以&#xff0c;我们抽一个基类&a…

高级数据结构与算法习题(9)

一、判断题 1、Let S be the set of activities in Activity Selection Problem. Then the earliest finish activity am​ must be included in all the maximum-size subset of mutually compatible activities of S. T F 解析:F。设S是活动选择问题中的一…

Golang Map类型

文章目录 Map介绍Map的定义方式Map的增删查改新增和修改Map元素查找Map元素删除Map元素遍历Map元素 Map元素排序Map切片 Map介绍 Map介绍 在Go中&#xff0c;map是哈希表的引用&#xff0c;是一种key-value数据结构。map类型写作map[K]V&#xff0c;其中K和V分别对应key和value…