用 CvANN_MLP(OpenCV 的神经网络-多层感知器) 进行路牌判别
原载于 用 CvANN_MLP 进行路牌判别。
这原是智能计算课程大作业。
(有时候我真的不知道怎么措辞,应该用“分类识别”,还是“判别”,还是“断别”?不在意这些细节。我说的是区分一张图片是不是路牌,就这么简单。)
使用 ANN-MLP(神经网络--多层感知器)方法。利用 Qt4、OpenCV2 程序库,进行路牌的抠取、分类和识别。开源在 GitHub:district10/SignProcessing: 路牌提取、分类,包括源码、文档、测试数据和可执行文件。其中的qt4cv3vs2015分支将 OpenCV 更新到了 OpenCV3,重新梳理、整合了各个模块。
快速入门
文档贴在这里,原载 Issues · district10/SignProcessing。
下载二进制
DLL 依赖
Qt 和 OpenCV 的 dll,以及 VS2015 的 runtime,点此下载 (11.2 MB),和下面的二进制执行档放到一起即可。
二进制执行档
Release 2, 2016-07-31, 962 KB
Release 1, 2016-07-30, 478 KB
如何用已训练模型来测试图片是否是路牌
SignProcessorDemo.exe
这个 exe 有三个功能,主要是用已经训练好的模型来预测(predict)。
功能 1,判别单张图片
加载一个图片,如果判定是一个路牌,用淡蓝色显示“Sign (pos)”:
如果不是一个路牌,用红色显示“Not Sign (neg)”:
功能 2,判别文件夹下所有图片
批量判断。选择一个文件夹(这里是 dir_of_images
)。
判断结果输出在文本框内,包括图片数目、每张图片的路径以及判别结果:
To Predict 38 images.processing D:/tzx/git/SignProcessing/data/input/dir_of_images/0002-team7 (109)-shift-sx-36-sy-16.bmp... done. assigned to [pos]...processing D:/tzx/git/SignProcessing/data/input/dir_of_images/dir2/dir22/0001-team7 (108)-rand-cx0623-cy0398-r135.bmp... done. assigned to [neg]
所选文件夹同一目录下还会出现两个目录 pos
和 neg
,分别把判别后的图片拷贝到里里面:
功能 3,判别选择的多张图片
判别为路牌的,标记“Pos”,不是路牌的,标记“Neg”。
**默认用了
data/output/4.xml
来预测,你也可以点击【Load Another XML】加载别的已训练模型。**
1)双击运行 SignClassifierDemo.exe
2)加载模型
点击【Load Trained】选择 XML 文件(在 data/output
文件夹)。
我这里选择了 4.xml
。
3)选择想要测试的图片
点击【Unknown】,选择一些图片(在 data/input/tests
文件夹)。
如下图,
还没有预测的图片下面有“???”标记,它表示还未测试。
然后点击【Predict】。
有的标记变成了“Pos”(是路牌),有得标记变成了“Neg”(不是路牌),
如下图:
如何从数据训练模型
1)双击运行 SignClassifierDemo.exe
2)添加正负样本
点击【Pos】,选择一些正样本。(可用 Control+A 全选)
同理,点击【Neg】,选择一些负样本。(加载可能有点慢)
3)训练
点击【Train】,训练开始。训练完成后,会提示已经训练的量。
记得点击【Save Trained】保存训练参数,下次就可以直接用【Load Trained】加载它了。
试试加载一些 Unknown,测试一下它的判断是否正确。
就是这样。
从源码编译
安装 VS2015 社区版
配置 Qt4 和 OpenCV3
下载源码:【qt4cv3vs2015 分支源码压缩包】
具体见 代码编译 · Issue #3 · district10/SignProcessing。
更进一步
可以看到,看到程序能基本准确地分辨哪些是路牌,哪些不是。现在我从数据采集、处理,程序编写来把整个流程说一遍。
源数据的处理
源数据是我们在武汉街头拍摄的路牌图片。大概像这样:
因为源图片里面有车牌之类的没有打码(哪有这空啊……),就不分享了。
从源图片中提取正负样本,用的是我们组写的 Sign Cutter(路牌切割)程序(源码),
利用它可以很快速地导入源影像,并进行切片处理。【保存切片】操作会把框选区域保存成一个切片图,作为我们人工选取的正样本。同时,正样本需要转化到 24 × 24 的 RGB 图像,为保证图片不失真,还要将之存储为 BMP 格式。
正样本为人工选取的路牌
因为正样本的数量有限,需要通过平移、旋转、镜像(镜像后再平移旋转)等方式从一张正样本产生多张类似正样本。在 SignCutter 中通过点击【生成正样本】实现。
![posnegclick]
负样本则是从图中非正样本区域,随机选取中心点和旋转角度,选取而来。正负样本选取比例我们设置为 1:7。通过点击【生成负样本】来快速地生成一系列负样本。结果就是,一张源图片,一个人工框选区域,生成了 87 张正样本,602 张负样本。最终我们生成了 8 组共 25,230 张正样本,69,894 张负样本,用于训练和检测。
你可以下载 图片索引,每一行代表一个样本,后面的 1 表示“是路牌”,0 表示“不是路牌”,大概长这样:
1/pos/0001-team7 (1)-___.bmp, 1
1/pos/0001-team7 (1)-___-r000.bmp, 1
1/pos/0001-team7 (1)-___-r030.bmp, 1
...
8/neg/0025-team7 (292)-rand-cx0469-cy2820-r108.bmp, 0
8/neg/0025-team7 (292)-rand-cx0471-cy2508-r232.bmp, 0
8/neg/0025-team7 (292)-rand-cx0473-cy2701-r358.bmp, 0
图片数据在这里下载:
1.7z 8.9M
2.7z 10M
3.7z 13M
4.7z 24M
5.7z 13M
6.7z 10M
7.7z 8.5M
8.7z 3.1M
正样本 87 张 & 负样本 602 张
为了保证得到的正样本足够好,我们不是在切片上进行这些操作,而是在源影像上,这就避免了旋转平移后图片中存在空白。这通过一个 SignLogger 模块实现(源码),它记录了切片的源图片、归一化了的中心点、归一化了的宽度和高度。
通过点击【查看切片记录】查看切片信息
这就是刚才那张路牌切片的 log 信息
整个正样本和负样本如图,红框内为切片区域,绿色矩形为正样本框(较密集),蓝色为负样本框(分散在源影像中非正样本区域)。
![signcutdemo]
从文件名也能看出每个切片的信息,这里是正负样本文件名的 Sample^[完整版可以在 http://gnat.qiniudn.com/sc/in... 下载。]
原图片 team7 (148).jpg 产生的正负样本.
├── pos (正样本 87 张)
│ ├── 0041-team7 (148)-___.bmp 原切片
│ ├── 0041-team7 (148)-___-r030.bmp 原切片旋转 30 度
│ ├──..............................
│ ├── 0041-team7 (148)-___-r330.bmp
│ ├── 0041-team7 (148)-L-R.bmp 左右镜像
│ ├── 0041-team7 (148)-L-R-r030.bmp 左右镜像后,旋转 30 度
│ ├── 0041-team7 (148)-L-R-r060.bmp
│ ├── 0041-team7 (148)-L-R-r090.bmp
│ ├── .............................
│ ├── 0041-team7 (148)-L-R-r330.bmp
│ ├── 0041-team7 (148)-shift-sx000-sy003.bmp 平移左右 0%,上下 3%
│ ├── .............................
│ ├── 0041-team7 (148)-shift-sx-10-sy-10.bmp
│ ├── 0041-team7 (148)-U-D.bmp 上下镜像
│ ├── .............................
│ └── 0041-team7 (148)-U-D-r330.bmp
├── neg (负样本 602 张)
│ ├── 0041-team7 (148)-rand-cx0057-cy1450-r305.bmp 随机中心点、旋转角度
│ ├── ............................................
│ └── 0041-team7 (148)-rand-cx2393-cy2979-r299.bmp
└── team7 (148)-demo.jpg (展示了正负样本切片位置)2 directories, 696 files
有了正负样本,就看用 OpenCV 提供的 ANN_MLP 进行基于神经网络方法的学习。这部分知识附在本文末尾。
从编码的角度,利用神经网络多层感知器分类的实际比图片预处理、正负样本的生成要简单,代码量也少很多。所以我从代码大概说一下使用方法。
首先,引入 OpenCV3 相关头文件:
#include <opencv2/core.hpp>
#include <opencv2/ml.hpp>
我封装了一个 MLP 类,提供简单的训练接口(源码)。这里是精简了的类声明:
class MLP
{
public:MLP(); // 构造函数,mlp 的初始化~MLP() { }void loadXML( const QString &xml ); // 加载已经训练好的模型void saveXML( const QString &xml ); // 保存训练好的模型void loadCSV( const QString &csv ); // 加载正负样本用于训练void train(); // 训练// 最后,对图片进行判别:是否为路牌// 输入为一串图片路径// 输出为图片路径和一个标志,true 说明图片为路牌,false 说明不是QList<QPair<QString, bool> > predictImages( const QStringList &images );private:cv::Ptr<cv::ml::ANN_MLP> mlp; // 存储了参数
};
其中在构造函数中,要实例化 mlp,还要对它进行配置:
mlp = cv::ml::ANN_MLP::create();
// 层数和每层 neuron 个数是“精选”出来的
mlp->setLayerSizes( (cv::Mat)(cv::Mat_<int>(1,5)<< FEATURENUM, FEATURENUM / 2, FEATURENUM / 6, FEATURENUM / 24, 1) );// 激活函数设置为 sigmoid
mlp->setActivationFunction( cv::ml::ANN_MLP::SIGMOID_SYM, 1.0, 1.0 );// 训练方法为反向传播
mlp->setTrainMethod( cv::ml::ANN_MLP::BACKPROP, 0.1, 0.1 );
mlp->setTermCriteria( cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 5000, 0.01 ) );
激活函数和反向传播的说明见本文末尾附录。反向传播一个比较好的文档见我的笔记:Principles of training multi-layer neural network using backpropagation。
Layer 的层数和每层的 neuron 数目对训练结果有较大影响,这是我之前测试后画的 Excel 表格:^[顺便学习了 Excel 的 minimap 的用法哈哈。]
设置好了,就可以用已经准备好的正负样本训练它。我们先不考虑的是如何从 24 × 24 的 RGB 图片,生成 feature 向量(也就是这里的 Utils::img2feature
函数的实现细节)。
// pos 和 neg 是图片路径列表,分别是正负样本集合
int np = pos.length();
int nn = neg.length();// 生成数据集,这里的 features 和 flags 就是机器学习里
// 常说的 data 和 label
float *features = new float[FEATURENUM*(np + nn)];
float *flags = new float[np + nn];// 用图片初始化 features,并设置好 labels
for ( int i = 0; i < np; ++i ) {Utils::img2feature(qPrintable(pos.at(i)), features + FEATURENUM*i);*(flags + i) = 1.0f; // 正样本为 1
}
for ( int i = 0; i < nn; ++i ) {Utils::img2feature(qPrintable(neg.at(i)), features + FEATURENUM*np+FEATURENUM*i);*(flags + np + i) = -1.0f; // 负样本为 -1
}// 存到 OpenCV 的矩阵结构 Mat 里,并生成数据集
cv::Mat featureMat( np+nn, FEATURENUM, CV_32F, features );
cv::Mat flagMat( np+nn, 1, CV_32F, flags );
cv::Ptr<cv::ml::TrainData> trainSet // mat 的每一行代表一条数据= cv::ml::TrainData::create( featureMat, cv::ml::ROW_SAMPLE, flagMat );// 然后就可以训练了
mlp->train( trainSet );// 不要忘了释放内存
delete[] features;
delete[] flags;
现在就能测试,
float feature[FEATURENUM];
Utils::img2feature( "path/to/image", feature );cv::Mat featureMat ( 1, FEATURENUM, CV_32F, feature );
cv::Mat result ( 1, 1, CV_32FC1 );mlp->predict( featureMat, result );
float *p = result.ptr<float>(0);
这个 *p
大于 0,则图片被判定为路牌,否则不是路牌。
我们还可以将训练好的模型保存起来,方便下载加载:
// 保存
mlp->save( "path/to/save/model(xml file)" );// 加载
mlp = cv::Algorithm::load<cv::ml::ANN_MLP>( "path/to/saved-model.xml" );
我训练了几组数据,得到的 xml 文件见 data/output
文件夹。大概长这样。
最后我们看看那个 Utils::img2feature
函数的实现(这里加上了额外的注释,做了额外的对齐):
// feature 已经分配好内存了,内存大小是 sizeof(float)*FEATURENUM
// FEATURENUM 是一个宏,定义为
// #define FEATURENUM ( 24*(3+3)+ 3 + 4*3 )
// 看了后面的数据处理你大概能懂这些数字每个代表什么bool Utils::img2feature( const char *filePath, float *feature )
{Mat img = imread( filePath, cv::IMREAD_COLOR );if ( img.rows != IMGSIZE || img.cols != IMGSIZE ) {qDebug() << __FUNCDNAME__ << "failed, because of wrong dim," << "\tfilepath:" << filePath;return false;}int allCountR = 0 , allCountG = 0 , allCountB = 0 ;int rowCountR[IMGSIZE] = { 0 }, rowCountG[IMGSIZE] = { 0 }, rowCountB[IMGSIZE] = { 0 };int colCountR[IMGSIZE] = { 0 }, colCountG[IMGSIZE] = { 0 }, colCountB[IMGSIZE] = { 0 };int tempR[4] = { 0 }, tempG[4] = { 0 }, tempB[4] = { 0 };for( int i = 0; i < IMGSIZE; ++i ) {for( int j = 0; j < IMGSIZE; ++j ) {int b = img.at<cv::Vec3b>(i,j)[0];int g = img.at<cv::Vec3b>(i,j)[1];int r = img.at<cv::Vec3b>(i,j)[2];allCountR += r; rowCountR[i] += r; colCountR[j] += r;allCountG += g; rowCountG[i] += g; colCountG[j] += g;allCountB += b; rowCountB[i] += b; colCountB[j] += b;/** 分成四个部分,统计各自区域内的 rgb 比例,四个区域低编号为:** 0 | 1* --+--* 2 | 3*/int index = 2 * (i < IMGSIZE / 2 ? 0 : 1) + (j < IMGSIZE / 2 ? 0 : 1);tempR[index] += r;tempG[index] += g;tempB[index] += b;}}for ( int i=0; i < IMGSIZE; ++i ) {feature[i*6+0] = (float)rowCountR[i]/allCountR; // 第 i 行的红色占全图红色的比例feature[i*6+1] = (float)rowCountG[i]/allCountG; // 绿色feature[i*6+2] = (float)rowCountB[i]/allCountB; // 蓝色feature[i*6+3] = (float)colCountR[i]/allCountR; // 第 i 列feature[i*6+4] = (float)colCountG[i]/allCountG;feature[i*6+5] = (float)colCountB[i]/allCountB;}// rgb 三种颜色的比例feature[IMGSIZE*6+0] = (float)allCountR / (allCountR + allCountG +allCountB);feature[IMGSIZE*6+1] = (float)allCountG / (allCountR + allCountG +allCountB);feature[IMGSIZE*6+2] = (float)allCountB / (allCountR + allCountG +allCountB);for ( int i = 0; i < 4; ++i ) {// 区域内的 rgb 比例int total = tempR[i] + tempG[i] + tempB[i];feature[IMGSIZE*6+3 + 3*i+0] = (float)tempR[i] / total;feature[IMGSIZE*6+3 + 3*i+1] = (float)tempG[i] / total;feature[IMGSIZE*6+3 + 3*i+2] = (float)tempB[i] / total;}return true;
}
用 CvANN_MLP(OpenCV 的神经网络-多层感知器) 进行路牌判别相关推荐
- OpenCV 神经网络 - 多层感知器(MLP)
一.简述 人工神经网络(ANN) 简称神经网络(NN),能模拟生物神经系统对物体所作出的交互反应,是由具有适应性的简单单元(称为神经元)组成的广泛并行互连网络. 二.M-P神经元 "M-P神 ...
- ANN-MLP(神经网络--多层感知器)
来自:https://docs.opencv.org/master/dc/dd6/ml_intro.html#ml_intro_ann 与ML中同时构建和训练的许多其他模型不同,在MLP模型中,这些步 ...
- 【Python-ML】神经网络-多层感知器增加梯度检验
# -*- coding: utf-8 -*- ''' Created on 2018年1月26日 @author: Jason.F @summary: 多层感知器实现,加梯度检验 训练集:http: ...
- 【Python-ML】神经网络-多层感知器
# -*- coding: utf-8 -*- ''' Created on 2018年1月26日 @author: Jason.F @summary: 多层感知器实现 训练集:http://yann ...
- 感知器的c++实现_使用FastAI和PyTorch的多层感知器
我将向您展示如何使用FastAIv1和Pytorch构建神经网络(多层感知器)并成功训练它以识别图像中的数字.Pytorch是一个非常流行的深度学习框架,FastAI v1是一个使用现代最佳实践简化训 ...
- 神经网络与机器学习 笔记—多层感知器(MLP)
多层感知器(MLP) Rosenblatt感知器和LMS算法,都是单层的并且是单个神经元构造的神经网络,他们的局限性是只能解决线性可分问题,例如Rosenblatt感知器一直没办法处理简单异或问题.然 ...
- keras从入门到放弃(六)多层感知器(神经网络)
多层感知器(神经网络) 从线性回归模型和对数几率回归模型本质上都是单个神经元 计算输入特征的加权 使用一个激活函数计算输出 单个神经元(二分类) 多和神经元(多分类) 但是单层神经元有缺陷 无法拟合& ...
- 神经网络入门回顾(感知器、多层感知器)
神经网络属于"联结主义",和统计机器学习的理论基础区别还是很不一样. 以我自己的理解,统计机器学习的理论基于统计学,理论厚度足够强,让人有足够的安全感:而神经网络的理论更侧重于代数 ...
- 多层感知器及常见激活函数-深度神经网络DNN及计算推导
多层感知器 在之前的博客中,我们了解到,感知器(指单层感知器)具有一定的局限--无法解决异或问题,即线性不可分的问题. 将多个单层感知器进行组合,就可以得到一个多层感知器(MLP--Multi-Lay ...
最新文章
- MSN空间上的以往技术贴整理
- 从零售商加速向零售平台进化,每日优鲜已经成为“生鲜电商第一股”
- Android被逼学习例子2
- 设计模式基于C#的工程化实现及扩展
- 黑马程序员-WEB前端与移动开发就业班
- beego 例子_beego框架代码分析
- 矩池云上安装ikatago及链接教程
- 因一个 Bug,Cassandra 4.0 暂停发布
- 关于Secureboot的简单介绍[结合rk平台]
- python爬取微博评论点赞数_爬取新浪微博评论及点赞数并存储为excel的.csv格式
- Python 高级:人工智能概述
- 怀念偶像科比布莱恩特--------Kobe Bryant
- H3C防火墙基础配置1-登录配置、安全域配置
- MFC版 黄金矿工 游戏开发记录
- 论文阅读笔记:《自然语言处理中的预训练模型》
- 登录邮箱怎么登录?163手机邮箱登录入口在这里
- 内部存储空间不足_手机存储空间不足教你快速解决
- CSS中如何实现背景图片透明并且固定和文字不透明效果
- 使用VB合并word文档内容
- 线性模型(一)--广义线性模型(GLM)简介
热门文章
- 【OpenHarmony-v3.2代码分析】02 - device目录 uboot源码分析
- 自动登录网易云获取近一周常听歌单
- macOS 10.14 mojave安装低版本iTunes 12.6.x (转载)
- “李佳琦”们,从卖货到卖品牌
- HTTP如何实现长链接
- 通过 BUILD.BRAND 获取的手机品牌列表
- 实体门店品牌怎么做私域流量运营,用企微SCRM系统布局生态
- Python异步编程入门篇
- 全国大学省英语竞赛【A类】剖析
- Android 13 首个开发者预览版到来