前一阵子一直在写关于图像识别的东西,说实话,大大小小的困难遇到了不少,有一些bug真的非常非常蠢。。。。。。现在看来,还是自己太菜,opencv学的不精,对神经网络的理解也不够透彻,当然也包括一些C++语言上的不熟练,所以也能看出,代码能力虽然一部分取决于理论知识,但更多的是练啊啊啊啊啊啊啊!!!
下面进入正题。
跟题目说的一样,项目是关于图像识别的。是可以用来识别照片或图片的内容,然后识别出来后转成对应的文本,然后再对文本进行相应的处理的这么一个东西。成型的想法是类似于小猿搜题的这种App,大致思路是把包含题目的图片送入识别系统,然后由识别系统识别出题目的文本内容,然后把内容交给服务器,服务器去请求网络访问,进而返回该题的最优的匹配结果,这是最基本的功能。而本篇文章所要讨论的是前一部分内容,即识别系统。
识别系统分为图像的预处理,图像分割,图像特征提取和图像识别。
预处理阶段包括二值化啊,灰度转换啊,腐蚀啊,膨胀啊等操作,目的就是为了降低噪声,减少数据量等。
图像分割阶段就是在预处理完之后,把图像中一行行的字符分割成单个字符,以便于后面对单个字符进行识别,方法我采用的是分别对行列进行投影的经典方法。代码如下:

for(int col=0;col<width;++col){for(int row=0;row<height;++row){PerPixelValue = image.at<uchar>(row,col);if(PerPixelValue == 255){vec[col]++;}}}for(int i = 0;i<Image.cols;++i){if(!InBlock && vec[i]!=0)//已进入字符区{InBlock = true;StartIndex = i;std::cout<<"StartIndex is"<<StartIndex<<std::endl;}else if(vec[i]==0 && InBlock)//进入空白区{EndIndex = i;InBlock = false;cv::Mat roiImg = Image(cv::Range(0,Image.rows),cv::Range(StartIndex,EndIndex+1));RoiList.push_back(roiImg);}}

特征提取方面可以选择的面有很多,我选择的是梯度分布特征和灰度统计特征。梯度特征指的是计算图像水平方向和竖直方向的梯度图像,然后通过给梯度图像分划不同的区域,进行梯度图像每个区域亮度值的统计。具体步骤如下:
<1>将字符由RGB转化为灰度,然后将图像归一化到16*8;

<2>定义soble水平检测算子:x_mask=[−1,0,1;−2,0,2;–1,0,1]和竖直方向梯度检测算子y_mask=x_mask的转置;

<3>对图像分别用mask_x和mask_y进行图像滤波得到SobelX和SobelY;

<4>对滤波后的图像,计算图像总的像素和,然后划分4*2的网络,计算每个网格内的像素值的总和;

<5>将每个网络内总灰度值占整个图像的百分比统计在一起写入一个向量,将两个方向各自得到的向量并在一起,共16维梯度特征向量;
梯度特征代码如下:

float mask[3][3] = { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } };cv::Mat y_mask = cv::Mat(3, 3, CV_32F, mask) / 8; cv::Mat x_mask = y_mask.t(); // 转置 cv::Mat sobelX, sobelY;filter2D(image, sobelX, CV_32F, x_mask); filter2D(image, sobelY, CV_32F, y_mask);sobelX = abs(sobelX); sobelY = abs(sobelY);float totaleValueX = sumMatValue(sobelX); float totaleValueY = sumMatValue(sobelY);// 将图像划分为4*2共8个格子,计算每个格子里灰度值总和的百分比 for (int i = 0; i < image.rows; i = i + 4) { for (int j = 0; j < image.cols; j = j + 4) { cv::Mat subImageX = sobelX(cv::Rect(j, i, 4, 4)); feat.push_back(sumMatValue(subImageX) / totaleValueX); cv::Mat subImageY= sobelY(cv::Rect(j, i, 4, 4)); feat.push_back(sumMatValue(subImageY) / totaleValueY); } }

而灰度特征比较简单,就是将图像的每个像素的值放到一个向量容器中,共16*8=128维特征向量。所以一幅图像的特征即为梯度特征和灰度特征的综合,共16+128=144维特征向量。
灰度特征代码如下:

for(int i=0;i<image.rows;i++){linePtr = image.ptr<uchar>(i);for(int j=0;j<image.cols;j++){feat.push_back((float)linePtr[j]);}}

对所有的训练样本都进行上述的特征选择和提取之后,把这些特征向量都保存在一个矩阵中作为BP神经网络的输入。分割好的单个字符同样也要经过上述特征提取,作为识别预测阶段的输入。

下面进入BP网络的训练阶段,我采用的是opencv的CvANN_MLP类和 CvANN_MLP_TrainParams类。用到的函数有:CvANN_MLP下的create、train、save、load和predict这几个函数。下面来介绍一下这几个函数的定义与作用。

1. void CvANN_MLP::create(const Mat& layerSizes, int activateFunc=CvANN_MLP::SIGMOID_SYM, double fparam1=0, double fparam2=0 );

各形参含义:
<1> layerSizes:一个整型的数组,这里面用Mat存储。它是一个1*N的Mat,N代表神经网络的层数,第ii列的值表示第ii层的结点数。这里需要注意的是,在创建这个Mat时,一定要是整型的,uchar和float型都会报错。可以这样定义:
layerSizes = (cv::Mat_<int>(1,3)<<x1,x2,x3);
即定义了一个三层的网络,x1,x2,x3分别是输入层,隐含层和输出层的节点数。注意,x1要和提取的特征维数保持一致,比如我上面介绍的提取的特征维数是144维,所以x1的值须为144;而x2的值没有明确规定,一般来说x2的值越大,即节点数越多,误差越小,但同时训练时间增加,并且容易陷入“过拟合”。而x2值设置过小的话,则会降低网络的分类能力。x3的值也要是确定的,x3的值等于总类别数,比如你要识别0-9数字,所以一共有10类,所以x3=10;
<2> activateFunc:用于指定激活函数,一般设置为SIGMOID函数即可,也可以选择正切函数或高斯函数,SIGMOID的值域为0~1。
综上,神经网络的创建调用如下:

CvANN_MLP *pMLP = new CvANN_MLP;
pMLP->create(layerSizes,CvANN_MLP::SIGMOID_SYM,1,1);
2. int CvANN_MLP::train(const Mat& inputs, const Mat& outputs, const Mat& sampleWeights, const Mat& sampleIdx=Mat(), CvANN_MLP_TrainParams params=CvANN_MLP_TrainParams(), int flags=0 );

各形参含义:
<1> inputs:输入矩阵。这个输入参数我上面提到过了,就是所有训练样本组成的一个特征值矩阵,每个样本的特征是m维,样本数若为n,则该inputs为n×m的矩阵;
<2> outputs:输出矩阵。若样本数为n,类别数为y,则outputs为n×y的矩阵,每一行表示每个样本的预期输出结果。比如0-9十类样本,每类样本数为50,故总有500个样本,所以输出矩阵有500行,10列。每一行默认有一列为1。假设500个样本是按类别输入的话,即先输入0的50个样本,再输入1的50个样本,那么输出矩阵的形式为:

输出矩阵的代码如下:

cv::Mat outputData = cv::Mat::zeros(1700, 34,CV_32FC1);int j=0;for(int i=0;i<1700;i++){outputData.at<float>(i, j) = 0.9999f;if((i!=0)&&(i%50==0)){j++;}}

相当于前50行第一列置1,51到100行第二列置1,以此类推。
综上,神经网络的训练调用如下:

pMLP->train(inputData,outputData,cv::Mat(),cv::Mat(),*pTrain,0);
3. save函数

该函数比较简单,用来保存经过训练后得到的权值矩阵,该函数也体现了BP网络的优越性,即在样本数不变的情况下,只需要经过一次训练,然后调用save函数保存训练得到的矩阵,识别的时候直接调用load加载进来即可。
调用格式如下:

pMLP->save("BPNET.xml");
4. load函数

上面提到了,加载训练好的xml。

5. float CvANN_MLP::predict(const Mat& inputs, Mat& outputs) const

各形参含义:
<1> inputs:输入矩阵。上面也有提到过。字符分割好后,对每个字符调用计算特征的函数,然后把得到的特征保存在一个矩阵中,即该输入矩阵。
<2> outputs:输出矩阵。这就是我们得到的最终识别结果了,是一个行数为1,列数为类别数的矩阵,其中每一列说明它与该类的相似程度(0-1之间),也可以说是置信度。如下图:

然后调用minMaxLoc函数对outputs求最大值,即返回最大值所在的列,上图即返回26,也就是说,分割后的某一单个字符属于第26类,对应一下即可得到第26类真正对应的字符。
识别的代码如下:

bp.predict(tres,nearest);cv::Point maxLoc;cv::minMaxLoc(nearest, NULL, NULL, NULL, &maxLoc);int result = maxLoc.x;

result即为所属类别。

以上,便是目前完成的部分,还有很多需要改进的部分。下面说说过程中遇到的问题和需要改进的地方。
一. 遇到的问题:

  1. 对opencv还是不太熟悉,对Mat类理解不够深刻,对相关函数理解不够,比如at()函数,是获取像素值的函数等等,走了很多弯路,查了大量资料。
  2. 在训练样本的批量读入方面无从下手,后来选择了opencv的Directory类,如果你也遇到类似问题,可以借鉴一下。需要声明头文件#include<opencv2/contrib/contrib.hpp>
    可以这样调用:
     cv::Directory dir;
     fileNames = dir.GetListFilesR(dir_path,"*",true);

所有的路径都保存在fileNames数组容器里,遍历读入即可。

  1. 在数组向矩阵转换的问题上挣扎了很久。因为我存储样本的特征向量时用的是vector容器,而无论train和predict函数的inputs都需要是矩阵,而且类型要float。经过查阅资料,cv::Mat tres(featRet,CV_32FC1);可以转换。转换结果tres虽然是个矩阵,并且实现了数组和矩阵的数据传递,但tres竟然是个144行1列的矩阵,而输入矩阵是列数为144.所以还需要对矩阵进行操作,tres = tres.t();实现矩阵转置。
  2. 解决了数组向矩阵的转换问题,另一个亟待解决的问题是,如何把一个矩阵的一行插入到另一个矩阵的另一行,或者说怎么按行对矩阵写入数据。因为每个样本的特征占据矩阵的一行,每计算完一个样本的特征就需要对矩阵进行写入,需要按顺序写入到每一行,解决办法是:
     cv::Mat dsttemp(1,144,CV_32FC1);dsttemp= InputMatix.row(i);tres.copyTo(dsttemp);

二. 需要改进的地方

  1. 图像分割方面
    目前只实现对只有一行的图像作为分割对象,后面要改进对含有多行多列的图像进行分割。而且目前的代码只对数字和英文字母有比较好的分割效果,汉字的分割还是不尽如人意,汉字分割难度还是较高的,这方面还需要查阅文献进行研究与改进。
  2. 样本集方面
    训练用的样本集变化程度不够,不够多样化,导致识别时换个不同的样式,置信率会很差,所以需要丰富样本集,使训练样本多样化。
  3. 神经网络训练方面
    对于隐含层节点数的把握还不够,对于如何选择区间节点数研究还不够,需要后续进行研究。

基于BP神经网络的图像识别之阶段性总结相关推荐

  1. 【图像识别】基于BP神经网络和RGB颜色空间实现人民币识别系统matlab代码

    1 简介 随着信息化时代的到来,智能识别成为研究的热点,本文以人民币识别为研究对象,运用 Matlab 软件系统中所提供的神经网络工具箱,结合图像处理技术,实现对各种不同面值纸质版人民币的识别.本文主 ...

  2. 【图像识别】基于BP神经网络实现手写体大写字母识别附matlab代码

    1 简介 手写体字符识别是人机交互领域的一个重要内容,本文基于 BP 神经网络实现了任意数量字符模版的多字符手写体字符识别.分为以下几步,第一,首先对目标图像进行识别前预处理.包括灰度图像二值化,图像 ...

  3. 【图像识别】基于 BP神经网络路面裂缝识别系统Matlab代码

    1 简介 随着我国经济建设的快速发展,道路交通在国民经济建设中扮演的角色越来越重要.随之而来的道路路面的养护和管理问题愈发凸显,其中道路路面的破损检测就成为相关道路养护部门的工作重点之一.另外,随着我 ...

  4. bp神经网络分类器c语言,基于BP神经网络的隐写分析分类器设计

    [ 摘 要 ] 设计并实现了基于BP神经网络的隐写分析分类器.首先对图像库中的图像进行格式变换,并使用扩展修改方向和钻石编码两种隐写方法进行不同嵌入率的隐写嵌入,然后计算载体图像和载密图像中平面域.D ...

  5. 基于BP神经网络的手写数字识别

    基于BP神经网络的手写数字识别 摘要 本文实现了基于MATLAB关于神经网络的手写数字识别算法的设计过程,采用神经网络中反向传播神经网络(即BP神经网络)对手写数字的识别,由MATLAB对图片进行读入 ...

  6. 《MATLAB 神经网络43个案例分析》:第25章 基于MIV的神经网络变量筛选----基于BP神经网络的变量筛选

    <MATLAB 神经网络43个案例分析>:第25章 基于MIV的神经网络变量筛选----基于BP神经网络的变量筛选 1. 前言 2. MATLAB 仿真示例 3. 小结 1. 前言 < ...

  7. 基于BP神经网络算法的性别识别

    目录 基于 BP 神经网络算法的性别识别 1 目录 1 1.背景介绍 2 2. OpenCV 的介绍 3 3.安装 OpenCV 4 4. BP 神经网络算法介绍和实践 4 4.1 BP 神经网络结构 ...

  8. ​【交通标志识别】基于BP神经网络实现交通标志识别matlab代码

    1 简介 近年来,交通标志识别在车辆视觉导航系统中是一个热门研究课题.为了安全驾驶和高效运输,交通部门在公路道路上设置了各类重要的交通标志,以提醒司机和行人有关道路交通信息,如指示标志.警告标志.禁止 ...

  9. MATLAB实现基于BP神经网络的手写数字识别+GUI界面+mnist数据集测试

    文章目录 MATLAB实现基于BP神经网络的手写数字识别+GUI界面+mnist数据集测试 一.题目要求 二.完整的目录结构说明 三.Mnist数据集及数据格式转换 四.BP神经网络相关知识 4.1 ...

最新文章

  1. [知识复习] 结构体以及结构体指针
  2. 【网络安全】简要分析下Chrome-V8-Issue-762874
  3. 这些基础协议,你懂吗?
  4. mysql安装 ---简单实用
  5. 操作系统设计与实现第3版笔记与minix3心得(5)-操作系统发展历史(3)
  6. 公益性岗位计算机考试内容,公益性岗位公共基础知识:计算机概述-计算机硬件系统(1)...
  7. 关于iOS APP 需要支持ipv6-only 开发者需要做的事情
  8. androidpn的一次亲密接触(二)
  9. 两个简单的前台显示构架01
  10. [码海拾贝 之Perl]在字符串数组中查找特定的字符串是否存在
  11. cenOS 安装opencv(for matlab)
  12. 计算机的管理员关机命令,电脑定时自动关机命令怎么使用?
  13. 实验吧CTF web刷题
  14. 看漫画来告诉你:什么是 “元宇宙” ?
  15. 机器学习算法工程师笔试及面试总结
  16. ARM920T及其MMU,Cache学习杂记(一)
  17. qt 禁止alt+f4_禁止上下关闭按钮和Alt + F4
  18. 计算机暑期学校心得,暑期学校培训心得体会(通用12篇)
  19. RFID固定资产管理系统,提高工作效率,节省时间-新导智能
  20. tws耳机哪个品牌好?2023年tws耳机排行

热门文章

  1. 分享180个美美的CSS渐变样式效果图
  2. 基于ESP8266上云实验
  3. iOS开发中生成随机数
  4. php取am+pm时间格式,php – 如何将带有日期和时间AM / PM的字符串转换为24小时的mysql时间戳格式...
  5. DVR 和 NVR 的区别
  6. win10如何设置定时联网断网辅助自律
  7. poj2389 Bull Math (高精度之A*B)
  8. 不动点迭代方程收敛判据及MATLAB实现
  9. 神经网络(五)卷积神经网络
  10. 苹果笔推荐购买吗?苹果平替笔推荐