-
Java OCR 图像智能字符识别技术,可识别中文
2012-02-08 09:58:34Java OCR 图像智能字符识别技术,可识别中文。具体详见:http://blog.csdn.net/white__cat/article/details/38461449 -
数字图像字符识别——数字识别
2018-04-19 15:14:02本文简单介绍图片字符识别的原理,主要识别图片中的数字,其他字符识别原理类似。大家应该知道,对于人类来说,可以很容易理解一张图片所表达的信息,这是人类视觉系统数万年演变进化的结果。但对于计算机这个诞生...本文简单介绍图片字符识别的原理,主要识别图片中的数字,其他字符识别原理类似。
大家应该知道,对于人类来说,可以很容易理解一张图片所表达的信息,这是人类视觉系统数万年演变进化的结果。但对于计算机这个诞生进化不到百年的 “新星”,要让它理解一张图像上的信息是一个复杂的过程。计算机理解图像是一个数字计算比较的过程。
如图,我们一目了然的识别的图像中的数字,如何让计算机识别下图中的数字呢?
环境:VS2015+openCV3.4.0
要识别图像字符,首先需要模版库。对于识别简单字符,可自己训练,也可网上下载数据集。笔者这里就直接用提前做好的(用photoshop制作,字体和字大小尽量和待识别字符相近,不然影响识别率),为便于识别,模版名就以数字命名。
1. 图片预处理。首先读取待识别图像,灰度化、二值化。
Mat srcImg = imread("H:\\test.jpg",CV_LOAD_IMAGE_GRAYSCALE);//打开图片
灰度后图像:
threshold(srcImg, srcImg, threshVal, 255, CV_THRESH_BINARY);//二值化
我们知道图像本身就以数字进行存储的,二值化后图像就只有两个值0和255.
例数字0:
注意二值化的阈值根据图像实际情况进行调整。
二值化图像后:
2. 图像分割。我们需要将图像中的字符分割开。整个过程分两部,左右分割和上下分割。基本思想是,从图像的左上角第一列开始,从左往右逐列扫描扫描,当遇到像素值为0时,记录该列号sCol,继续扫描再遇到整列像素值为255时,记录列号eCol,sCol与eCol之间即为字符所在区间。复制二值图该区域,这样完成了左右分割;
将左右分割后,在此基础上再对图像进行上下分割,同理,从图像左上角第一行,向下逐行扫描,当遇到像素值为255时,记录该列号sRow,继续扫描再遇到整列像素值为0时,记录列号eRow,sRow与eRow之间即为字符所在区间。复制该区域,这样完成了上下左右分割。
3. 识别。把切割后的数字图片大小调整到和模板一样的大小(一般以模版中最大尺寸),然后让需要匹配的图和别和10个模板相减(让两个图片对应坐标像素点值相减),将所有差的绝对值求和 。
最后与哪个模板匹配时绝对值和最小,则就可以得到图像与该模版最匹配,进而识别该字符。
4. 结果。
-
字符识别OCR研究 字符识别,字符区域定位
2015-07-29 13:11:30字符识别经验总结: 一、 视频帧中字符的识别(video ocr): 难点1:视频流中,出现字符后,立即开始采集含有字符的视频帧; 难点2:对视频帧中字符区域的定位; 难点3:快速有效的识别出字符; ...字符识别经验总结:
一、 视频帧中字符的识别(video ocr):
难点1:视频流中,出现字符后,立即开始采集含有字符的视频帧;
难点2:对视频帧中字符区域的定位;
难点3:快速有效的识别出字符;
其中视频帧中字符区域的定位有多种方法:
(1) 基于梯度的方式:
字符最大的特征就是梯度性很多,利用字符的梯度特征,定位字符区域。
具体做法为:区域梯度变换频繁的特点,利用高通滤波保留梯度变换频繁区域,形态学处理后,将相邻区域进行合并以确定车牌的候选区域。
(2)基于字符纹理的方法:
先对字符区域小波变化,在利用字符的纹理信息提取字符区域
字符区域的分割定位,参考这里。
http://blog.csdn.net/zhubenfulovepoem/article/details/7280961
(3)基于灰度直方图的方式:
参考这篇文章http://wenku.baidu.com/view/d1b07f0e76c66137ee061941.html
非压缩域数字视频中文字的检测与提取
二、字符的特征描述:
(1)一种很有效的方式就是利用字符的Blob块:
http://www.cnblogs.com/xiaotie/archive/2010/05/23/1741946.html
基本识别过程是:
1、Blob特征识别,将图像分为几大组。
2、对每一组使用模板匹配,进行识别。
3、对于(2)中一些易混淆结果,再使用Blob特征,进行识别。
(2)利用尺度不变性特征:不受角度变化的影响
具体有sift,suft,orb(orb是sift的100倍,suft的10倍)。
ORB测试效果:
http://www.cvchina.info/2011/07/04/whats-orb/
ORB是是ORiented Brief的简称。ORB的描述在下面文章中:
Ethan Rublee and Vincent Rabaud and Kurt Konolige and GaryBradski, ORB: an efficient alternative to SIFT or SURF, ICCV 2011
Brief是Binary Robust Independent Elementary Features的缩写。这个特征描述子是由EPFL的Calonder在ECCV2010上提出的。主要思路就是在特征点附近随机选取若干点对,将这些点对的灰度值的大小,组合成一个二进制串,并将这个二进制串作为该特征点的特征描述子。详细算法描述参考如下论文:
CalonderM., Lepetit V., Strecha C., Fua P.: BRIEF: Binary Robust Independent ElementaryFeatures. ECCV 2010
注意在BRIEFeccv2010的文章中,BRIEF描述子中的每一位是由随机选取的两个像素点做二进制比较得来的。文章同样提到,在此之前,需要选取合适的gaussian kernel对图像做平滑处理。(为什么要强调这一点,因为下述的ORB对此作了改进。)
BRIEF的优点在于速度,缺点也相当明显:
1:不具备旋转不变性。
2:对噪声敏感
3:不具备尺度不变性。
ORB就是试图解决上述缺点中的1和2.
如何解决旋转不变性:
在ORB的方案中,是采用了FAST作为特征点检测算子。
(3)直接利用像素值:
将样本分成8*4共32块,统计每一块中的黑色像素所占的每一块中总像素值的比率,作为特征值,这样,每一个待识别的样本就有一组一行32列的数组作为特征。
特征选取后的预处理,可以用PCA主成分分析提取主要特征。
细节参考这本书 模式识别与智能计算 -------Matlab 技术实现
作 者: 杨淑莹著
http://baike.baidu.com/view/2033794.htm
(4)另外还有特征;可以针对变态字符的识别
http://www.cnblogs.com/xiaotie/archive/2009/01/15/1376677.html
详细情况可以参考上述文章的说明:
1、 切线距离:tangent distance可以处理字符的各种变形
上图有两条曲线。分别是两个字符经过某一形变后所产生的轨迹。V1和V2是2个样本。V’是待识别图片。如果用样本之间的直接距离,比较哪个样本离V’最近,就将V’当作哪一类,这样的话,就要把V’分给V1了。理论上,如果我们无限取样的话,下面那一条曲线上的某个样本离V’最近,V’应该归类为V2。不过,无限取样不现实,于是就引出了切线距离:在样本V1,V2处做切线,然后计算V’离这两条切线的距离,哪个最近就算哪一类。这样一来,每一个样本,就可以代表它附近的一个样本区域,不需要海量的样本,也能有效的计算不同形状间的相似性。
2、霍夫变换;Hough distance 对噪声极不敏感,可用于从图中提取各种形状
通过霍夫变换原理可以看出,它的抗干扰性极强极强:如果直线不是连续的,是断断续续的,变换之后仍然是一个点,只是这个点的强度要低一些。如果一个直线被一个矩形遮盖住了,同样不影响识别。因为这个特征,它的应用性非常广泛。
3、 形状上下文:shape context 特征高维化,对形变极不敏感,对噪声也不敏感。
图像中的像素点不是孤立的,每个像素点,处于一个形状背景之下,因此,在提取特征时,需要将像素点的背景也作为该像素点的特征提取出来,数值化。
形状上下文(ShapeContext,形状背景)就是这样一种方法:假定要提取像素点O的特征,采用上图(c)中的坐标系,以O点作为坐标系的圆心。这个坐标系将O点的上下左右切割成了12×5=60小块,然后统计这60小块之内的像素的特征,将其数值化为12×5的矩阵,上图中的(d),(e),(f)便分别是三个像素点的ShapeContext数值化后的结果。如此一来,提取的每一个点的特征便包括了形状特征,加以计算,威力甚大。
Shape Context是新出现的方法,其威力到底有多大目前还未见底。这篇文章是Shape context的必读文章:Shape Matching andObject Recognitiom using shape contexts(http://www.cs.berkeley.edu/~malik/papers/BMP-shape.pdf
)。最后那两张验证码识别图出自Greg Mori,JitendraMalik的《Recognizing Objectsin Adversarial Clutter:Breaking a Visual CAPTCHA》一文。
三、 识别方式(分类器研究)
(1) ANN人工神经网络(BP、RBF)
(2) SVM支持向量机
(3) 流行学习的方法
(4) 模板匹配,利用距离准则函数进行分类
常用的距离准则函数有:
(5)Bayes等等,不再介绍了,有机会再更新。
-
印刷喷码字符识别,数段字符识别:易拉罐底字符识别开发说明书
2016-04-20 22:16:41多易拉罐底字符识别开发说明书本系统分为:图像预处理,单易拉罐定位,字符区域块定位,字符识别四大块。1、图像预处理包括,对不均匀光照的处理,通过直方图拉伸等手段对图像进行亮度区域选择,突出字符区域的亮度...本系统分为:图像预处理,单易拉罐定位,字符区域块定位,字符识别四大块。
1、图像预处理包括,对不均匀光照的处理,通过直方图拉伸等手段对图像进行亮度区域选择,突出字符区域的亮度分布。(说明:由于系统实时性的要求,尽量减少预处理过程。)
2、单易拉罐定位:当一幅图像中出现多个易拉罐的时候,首先要定位到单个易拉罐,分析易拉罐的形状,可以采用基于易拉罐外形的形状匹配思路来定位到单个易拉罐或者采用基于圆检测可以采用hough圆检测或者小波变换的圆检测来定位到单个易拉罐;
3、字符区域块定位:由于字符区域块具有旋转的特点,这里采用形态学的思路,先使字符区域膨胀粘连成一个整体,然后检测轮廓,通过长宽比和面积筛选轮廓,可以确定字符区域块的位置,最后根据字符区域块的轮廓拟合出的矩形的倾斜角度做矫正就可以得到字符区域块的水平矫正后的区域。
4、字符识别:包括单个字符区域的分割,字符样本筛选和处理,字符训练,字符识别。
(1)单个字符的分割:在第三步处理好的倾斜矫正后的图像上,先垂直投影,将字符区域行分割出来;对于单行的字符区域,做水平投影,然后根据字符宽固定的思路做字符分割,分割出单个的字符。
(2)对单个的字符按类别分类好,并对样本进行初步的预处理,增强,归一化。
将所有的样本归一化为28*28区域大小。
(3)采用CNN卷积神经网络对字符进行训练。
本项目采用的卷积结构说明:
第一层仅具有一个特征映射是输入图像本身。在下面的层中,每个特征映射保持一定数目的独特的内核(权重的二维阵列),等于先前层中的特征映射的数量。在特征图中的每个内核的大小是相同的,并且是一个设计参数。在特征图中的像素值是通过卷积其内核与前一层中的相应特征映射推导。在最后一层的特征映射的数目等于输出选项的数目。例如在0-9数字识别,会有在输出层10的特征图,并具有最高的像素值的特征映射将结果。#include ".\cnn.h" #include <stdlib.h> #include <stdio.h> #include <time.h> /*用到了time函数,所以要有这个头文件*/ CCNN::CCNN(void) { ConstructNN(); } CCNN::~CCNN(void) { DeleteNN(); } #ifndef max #define max(a,b) (((a) > (b)) ? (a) : (b)) #endif //////////////////////////////////// void CCNN::ConstructNN() //////////////////////////////////// { int i; m_nLayer = 5; m_Layer = new Layer[m_nLayer]; m_Layer[0].pLayerPrev = NULL; for(i=1; i<m_nLayer; i++) m_Layer[i].pLayerPrev = &m_Layer[i-1]; m_Layer[0].Construct ( INPUT_LAYER, 1, 29, 0, 0 ); m_Layer[1].Construct ( CONVOLUTIONAL, 6, 13, 5, 2 ); m_Layer[2].Construct ( CONVOLUTIONAL, 50, 5, 5, 2 ); m_Layer[3].Construct ( FULLY_CONNECTED, 100, 1, 5, 1 ); m_Layer[4].Construct ( FULLY_CONNECTED, 10, 1, 1, 1 ); } /////////////////////////////// void CCNN::DeleteNN() /////////////////////////////// { //SaveWeights("weights_updated.txt"); for(int i=0; i<m_nLayer; i++) m_Layer[i].Delete(); } ////////////////////////////////////////////// void CCNN::LoadWeightsRandom() ///////////////////////////////////////////// { int i, j, k, m; srand((unsigned)time(0)); for ( i=1; i<m_nLayer; i++ ) { for( j=0; j<m_Layer[i].m_nFeatureMap; j++ ) { m_Layer[i].m_FeatureMap[j].bias = 0.05 * RANDOM_PLUS_MINUS_ONE; for(k=0; k<m_Layer[i].pLayerPrev->m_nFeatureMap; k++) for(m=0; m < m_Layer[i].m_KernelSize * m_Layer[i].m_KernelSize; m++) m_Layer[i].m_FeatureMap[j].kernel[k][m] = 0.05 * RANDOM_PLUS_MINUS_ONE; } } } ////////////////////////////////////////////// void CCNN::LoadWeights(char *FileName) ///////////////////////////////////////////// { int i, j, k, m, n; FILE *f; if((f = fopen(FileName, "r")) == NULL) return; for ( i=1; i<m_nLayer; i++ ) { for( j=0; j<m_Layer[i].m_nFeatureMap; j++ ) { fscanf(f, "%lg ", &m_Layer[i].m_FeatureMap[j].bias); for(k=0; k<m_Layer[i].pLayerPrev->m_nFeatureMap; k++) for(m=0; m < m_Layer[i].m_KernelSize * m_Layer[i].m_KernelSize; m++) fscanf(f, "%lg ", &m_Layer[i].m_FeatureMap[j].kernel[k][m]); } } fclose(f); } ////////////////////////////////////////////// void CCNN::SaveWeights(char *FileName) ///////////////////////////////////////////// { int i, j, k, m; FILE *f; if((f = fopen(FileName, "w")) == NULL) return; for ( i=1; i<m_nLayer; i++ ) { for( j=0; j<m_Layer[i].m_nFeatureMap; j++ ) { fprintf(f, "%lg ", m_Layer[i].m_FeatureMap[j].bias); for(k=0; k<m_Layer[i].pLayerPrev->m_nFeatureMap; k++) for(m=0; m < m_Layer[i].m_KernelSize * m_Layer[i].m_KernelSize; m++) { fprintf(f, "%lg ", m_Layer[i].m_FeatureMap[j].kernel[k][m]); } } } fclose(f); } ////////////////////////////////////////////////////////////////////////// int CCNN::Calculate(double *input, double *output) ////////////////////////////////////////////////////////////////////////// { int i, j; //copy input to layer 0 for(i=0; i<m_Layer[0].m_nFeatureMap; i++) for(j=0; j < m_Layer[0].m_FeatureSize * m_Layer[0].m_FeatureSize; j++) m_Layer[0].m_FeatureMap[0].value[j] = input[j]; //forward propagation //calculate values of neurons in each layer for(i=1; i<m_nLayer; i++) { //initialization of feature maps to ZERO for(j=0; j<m_Layer[i].m_nFeatureMap; j++) m_Layer[i].m_FeatureMap[j].Clear(); //forward propagation from layer[i-1] to layer[i] m_Layer[i].Calculate(); } //copy last layer values to output for(i=0; i<m_Layer[m_nLayer-1].m_nFeatureMap; i++) output[i] = m_Layer[m_nLayer-1].m_FeatureMap[i].value[0]; ///================================ /*FILE *f; char fileName[100]; for(i=0; i<m_nLayer; i++) { sprintf(fileName, "layer0%d.txt", i); f = fopen(fileName, "w"); for(j=0; j<m_Layer[i].m_nFeatureMap; j++) { for(k=0; k<m_Layer[i].m_FeatureSize * m_Layer[i].m_FeatureSize; k++) { if(k%m_Layer[i].m_FeatureSize == 0) fprintf(f, "\n"); fprintf(f, "%10.7lg\t", m_Layer[i].m_FeatureMap[j].value[k]); } fprintf(f, "\n\n\n"); } fclose(f); }*/ ///================================== //get index of highest scoring output feature j = 0; for(i=1; i<m_Layer[m_nLayer-1].m_nFeatureMap; i++) if(output[i] > output[j]) j = i; return j; } /////////////////////////////////////////////////////////// void CCNN::BackPropagate(double *desiredOutput, double eta) /////////////////////////////////////////////////////////// { int i ; //derivative of the error in last layer //calculated as difference between actual and desired output (eq. 2) for(i=0; i<m_Layer[m_nLayer-1].m_nFeatureMap; i++) { m_Layer[m_nLayer-1].m_FeatureMap[i].dError[0] = m_Layer[m_nLayer-1].m_FeatureMap[i].value[0] - desiredOutput[i]; } double mse=0.0; for ( i=0; i<10; i++ ) { mse += m_Layer[m_nLayer-1].m_FeatureMap[i].dError[0] * m_Layer[m_nLayer-1].m_FeatureMap[i].dError[0]; } //backpropagate through rest of the layers for(i=m_nLayer-1; i>0; i--) { m_Layer[i].BackPropagate(1, eta); } ///================================ //for debugging: write dError for each feature map in each layer to a file /* FILE *f; char fileName[100]; for(i=0; i<m_nLayer; i++) { sprintf(fileName, "backlx0%d.txt", i); f = fopen(fileName, "w"); for(j=0; j<m_Layer[i].m_nFeatureMap; j++) { for(k=0; k<m_Layer[i].m_FeatureSize * m_Layer[i].m_FeatureSize; k++) { if(k%m_Layer[i].m_FeatureSize == 0) fprintf(f, "\n"); fprintf(f, "%10.7lg\n", m_Layer[i].m_FeatureMap[j].dError[k]); } fprintf(f, "\n\n\n"); } fclose(f); } //--------------------------------- //for debugging: write dErr_wrtw for each feature map in each layer to a file for(i=1; i<m_nLayer; i++) { sprintf(fileName, "backlw0%d.txt", i); f = fopen(fileName, "w"); for(j=0; j<m_Layer[i].m_nFeatureMap; j++) { fprintf(f, "%10.7lg\n", m_Layer[i].m_FeatureMap[j].dErr_wrtb); for(k=0; k<m_Layer[i].pLayerPrev->m_nFeatureMap; k++) { for(int m=0; m < m_Layer[i].m_KernelSize * m_Layer[i].m_KernelSize; m++) { fprintf(f, "%10.7lg\n", m_Layer[i].m_FeatureMap[j].dErr_wrtw[k][m]); } fprintf(f, "\n\n\n"); } } fclose(f); } ///================================== */ int t=0; } //////////////////////////////////////////////// void CCNN::CalculateHessian( ) //////////////////////////////////////////////// { int i, j, k ; //2nd derivative of the error wrt Xn in last layer //it is always 1 //Xn is the output after applying SIGMOID for(i=0; i<m_Layer[m_nLayer-1].m_nFeatureMap; i++) { m_Layer[m_nLayer-1].m_FeatureMap[i].dError[0] = 1.0; } //backpropagate through rest of the layers for(i=m_nLayer-1; i>0; i--) { m_Layer[i].BackPropagate(2, 0); } //average over the number of samples used for(i=1; i<m_nLayer; i++) { for(j=0; j<m_Layer[i].m_nFeatureMap; j++) { m_Layer[i].m_FeatureMap[j].diagHessianBias /= g_cCountHessianSample; for(k=0; k<m_Layer[i].pLayerPrev->m_nFeatureMap; k++) { for(int m=0; m < m_Layer[i].m_KernelSize * m_Layer[i].m_KernelSize; m++) { m_Layer[i].m_FeatureMap[j].diagHessian[k][m] /= g_cCountHessianSample; } } } } ////------------------------------- //saving to files for debugging /*FILE *f; char fileName[100]; for(i=1; i<m_nLayer; i++) { sprintf(fileName, "hessian0%d.txt", i); f = fopen(fileName, "w"); for(j=0; j<m_Layer[i].m_nFeatureMap; j++) { fprintf(f, "%10.7lg\n", m_Layer[i].m_FeatureMap[j].diagHessianBias); for(k=0; k<m_Layer[i].pLayerPrev->m_nFeatureMap; k++) { for(int m=0; m < m_Layer[i].m_KernelSize * m_Layer[i].m_KernelSize; m++) { fprintf(f, "%10.7lg\n", m_Layer[i].m_FeatureMap[j].diagHessian[k][m]); } fprintf(f, "\n\n\n"); } } fclose(f); }*/ ///--------------------------------------- int t=0; } ///////////////////////////////////////////////////////////////////////////////////////////////////// void Layer::Construct(int type, int nFeatureMap, int FeatureSize, int KernelSize, int SamplingFactor) ///////////////////////////////////////////////////////////////////////////////////////////////////// { m_type = type; m_nFeatureMap = nFeatureMap; m_FeatureSize = FeatureSize; m_KernelSize = KernelSize; m_SamplingFactor = SamplingFactor; m_FeatureMap = new FeatureMap[ m_nFeatureMap ]; for(int j=0; j<m_nFeatureMap; j++) { m_FeatureMap[j].pLayer = this; m_FeatureMap[j].Construct( ); } } ///////////////////////// void Layer::Delete() ///////////////////////// { for(int j=0; j<m_nFeatureMap; j++) m_FeatureMap[j].Delete(); } /////////////////////////////////////////////////// void Layer::Calculate() //forward propagation /////////////////////////////////////////////////// { for(int i=0; i<m_nFeatureMap; i++) { //initialize feature map to bias for(int k=0; k < m_FeatureSize * m_FeatureSize; k++) { m_FeatureMap[i].value[k] = m_FeatureMap[i].bias; } //calculate effect of each feature map in previous layer //on this feature map in this layer for(int j=0; j<pLayerPrev->m_nFeatureMap; j++) { m_FeatureMap[i].Calculate( pLayerPrev->m_FeatureMap[j].value, //input feature map j //index of input feature map ); } //SIGMOD function for(int j=0; j < m_FeatureSize * m_FeatureSize; j++) { m_FeatureMap[i].value[j] = 1.7159 * tanh(0.66666667 * m_FeatureMap[i].value[j]); } //print(i); //for debugging } } /////////////////////////////////////////////////////////////// void Layer::BackPropagate(int dOrder, double etaLearningRate) ////////////////////////////////////////////////////////////// { //find dError (2nd derivative) wrt the actual output Yn of this layer //Note that SIGMOID was applied to Yn to get Xn during forward propagation //We already have dErr_wrt_dXn calculated in CCNN :: BackPropagate and //use the following equation to get dErr_wrt_dYn //dErr_wrt_dYn = InverseSIGMOID(Xn)^2 * dErr_wrt_dXn for(int i=0; i<m_nFeatureMap; i++) { for(int j=0; j < m_FeatureSize * m_FeatureSize; j++) { double temp = DSIGMOID(m_FeatureMap[i].value[j]); if(dOrder == 2) temp *= temp; m_FeatureMap[i].dError[j] = temp * m_FeatureMap[i].dError[j]; } } //clear dError wrt weights for(int i=0; i<m_nFeatureMap; i++)m_FeatureMap[i].ClearDErrWRTW(); //clear dError wrt Xn in previous layer. //This is input to the previous layer for backpropagation for(int i=0; i<pLayerPrev->m_nFeatureMap; i++) pLayerPrev->m_FeatureMap[i].ClearDError(); //Backpropagate for(int i=0; i<m_nFeatureMap; i++) { //derivative of error wrt bias for(int j=0; j<m_FeatureSize * m_FeatureSize; j++) m_FeatureMap[i].dErr_wrtb += m_FeatureMap[i].dError[j]; //calculate effect of this feature map on each feature map in the revious layer for(int j=0; j<pLayerPrev->m_nFeatureMap; j++) { m_FeatureMap[i].BackPropagate( pLayerPrev->m_FeatureMap[j].value, //input feature map j, //index of input feature map pLayerPrev->m_FeatureMap[j].dError, //dErr_wrt_Xn for previous layer dOrder //order of derivative ); } } //update weights (for backporpagation) or diagonal hessian (for 2nd order backpropagation) double epsilon, divisor; for(int i=0; i<m_nFeatureMap; i++) { if(dOrder == 1) { divisor = max(0, m_FeatureMap[i].diagHessianBias) + dMicronLimitParameter; epsilon = etaLearningRate / divisor; m_FeatureMap[i].bias -= epsilon * m_FeatureMap[i].dErr_wrtb; } else { m_FeatureMap[i].diagHessianBias += m_FeatureMap[i].dErr_wrtb; } for(int j=0; j<pLayerPrev->m_nFeatureMap; j++) { for(int k=0; k < m_KernelSize * m_KernelSize; k++) { if(dOrder == 1) { divisor = max(0, m_FeatureMap[i].kernel[j][k]) + dMicronLimitParameter; epsilon = etaLearningRate / divisor; m_FeatureMap[i].kernel[j][k] -= epsilon * m_FeatureMap[i].dErr_wrtw[j][k]; } else { m_FeatureMap[i].diagHessian[j][k] += m_FeatureMap[i].dErr_wrtw[j][k]; } } } } } ////////////////////////////////////////////////////////////////////////////////////////// void FeatureMap::Construct( ) ////////////////////////////////////////////////////////////////////////////////////////// { if(pLayer->m_type == INPUT_LAYER) m_nFeatureMapPrev = 0; else m_nFeatureMapPrev = pLayer->pLayerPrev->m_nFeatureMap; int FeatureSize = pLayer->m_FeatureSize; int KernelSize = pLayer->m_KernelSize; //neuron values value = new double [ FeatureSize * FeatureSize ]; //error in neuron values dError = new double [ FeatureSize * FeatureSize ]; //weights kernel kernel = new double* [ m_nFeatureMapPrev ]; for(int i=0; i<m_nFeatureMapPrev; i++) { kernel[i] = new double [KernelSize * KernelSize]; //initialize bias = 0.05 * RANDOM_PLUS_MINUS_ONE; for(int j=0; j < KernelSize * KernelSize; j++) kernel[i][j] = 0.05 * RANDOM_PLUS_MINUS_ONE; } //diagHessian diagHessian = new double* [ m_nFeatureMapPrev ]; for(int i=0; i<m_nFeatureMapPrev; i++) diagHessian[i] = new double [KernelSize * KernelSize]; //derivative of error wrt kernel weights dErr_wrtw = new double* [ m_nFeatureMapPrev ]; for(int i=0; i<m_nFeatureMapPrev; i++) dErr_wrtw[i] = new double [KernelSize * KernelSize]; } /////////////////////////// void FeatureMap::Delete() /////////////////////////// { delete[] value; delete[] dError; for(int i=0; i<m_nFeatureMapPrev; i++) { delete[] kernel[i]; delete[] dErr_wrtw[i]; delete[] diagHessian[i]; } } //////////////////////////// void FeatureMap::Clear() ///////////////////////////// { for(int i=0; i < pLayer->m_FeatureSize * pLayer->m_FeatureSize; i++) value[i] = 0.0; } //////////////////////////////// void FeatureMap::ClearDError() ///////////////////////////////// { for(int i=0; i < pLayer->m_FeatureSize * pLayer->m_FeatureSize; i++) dError[i] = 0.0; } /////////////////////////////////// void FeatureMap::ClearDiagHessian() /////////////////////////////////// { diagHessianBias = 0; for(int i=0; i < m_nFeatureMapPrev; i++) for(int j=0; j < pLayer->m_KernelSize * pLayer->m_KernelSize; j++) diagHessian[i][j] = 0.0; } //////////////////////////////// void FeatureMap::ClearDErrWRTW() //////////////////////////////// { dErr_wrtb = 0; for(int i=0; i < m_nFeatureMapPrev; i++) for(int j=0; j < pLayer->m_KernelSize * pLayer->m_KernelSize; j++) dErr_wrtw[i][j] = 0.0; } ////////////////////////////////////////////////////////////////////////////////////////////////////// void FeatureMap::Calculate(double *valueFeatureMapPrev, int idxFeatureMapPrev ) ////////////////////////////////////////////////////////////////////////////////////////////////////// //calculate effect of a feature map in previous layer on this feature map in this layer // valueFeatureMapPrev: feature map in previous layer // idxFeatureMapPrev : index of feature map in previous layer { int isize = pLayer->pLayerPrev->m_FeatureSize; //feature size in previous layer int ksize = pLayer->m_KernelSize; int step_size = pLayer->m_SamplingFactor; int k = 0; for(int row0 = 0; row0 <= isize - ksize; row0 += step_size) for(int col0 = 0; col0 <= isize - ksize; col0 += step_size) value[k++] += Convolute(valueFeatureMapPrev, isize, row0, col0, kernel[idxFeatureMapPrev], ksize); } ////////////////////////////////////////////////////////////////////////////////////////////////////// double FeatureMap::Convolute(double *input, int size, int r0, int c0, double *weight, int kernel_size) ////////////////////////////////////////////////////////////////////////////////////////////////////// { int i, j, k = 0; double summ = 0; for(i = r0; i < r0 + kernel_size; i++) for(j = c0; j < c0 + kernel_size; j++) summ += input[i * size + j] * weight[k++]; return summ; } ///////////////////////////////////////////////////////////////////////////////////// void FeatureMap::BackPropagate(double *valueFeatureMapPrev, int idxFeatureMapPrev, double *dErrorFeatureMapPrev, int dOrder ) ///////////////////////////////////////////////////////////////////////////////////// //calculate effect of this feature map on a feature map in previous layer //note that previous layer is next in backpropagation // valueFeatureMapPrev: feature map in previous layer // idxFeatureMapPrev : index of feature map in previous layer // dErrorFeatureMapPrev: dError wrt neuron values in the FM in prev layer { int isize = pLayer->pLayerPrev->m_FeatureSize; //size of FM in previous layer int ksize = pLayer->m_KernelSize; //kernel size int step_size = pLayer->m_SamplingFactor; //subsampling factor int row0, col0, k; k = 0; for(row0 = 0; row0 <= isize - ksize; row0 += step_size) { for(col0 = 0; col0 <= isize - ksize; col0 += step_size) { for(int i=0; i<ksize; i++) { for(int j=0; j<ksize; j++) { //get dError wrt output for feature map in the previous layer double temp = kernel[idxFeatureMapPrev][i * ksize + j]; if(dOrder == 1) dErrorFeatureMapPrev[(row0 + i) * isize + (j + col0)] += dError[k] * temp; else dErrorFeatureMapPrev[(row0 + i) * isize + (j + col0)] += dError[k] * temp * temp; //get dError wrt kernel wights temp = valueFeatureMapPrev[(row0 + i) * isize + (j + col0)]; if(dOrder == 1) dErr_wrtw[idxFeatureMapPrev][i * ksize + j] += dError[k] * temp; else dErr_wrtw[idxFeatureMapPrev][i * ksize + j] += dError[k] * temp * temp; } } k++; } } }
在这项研究中实现显示在上图中。在每一层的特征映射(FM),以及它们的大小的数列的每个层下。例如第1层有6个特征图,每一个尺寸为13×13的。内核各特征映射在一个层中含有的数目也示于图中。例如,W [6] [25]根据第2层的书面指示对每个特征图中该层有6个内核(等于调频的前层中的数量),每个都具有25的权重(5x5的阵列)。除了这些权重,每个FM有一个偏置的重量(它的重要性,请参考任何NN文本)。每个像素具有另一个参数SF(采样系数),它会在向前传播部分进行说明。其他三个参数DBIAS,dErrorW和dErrorFM将在后面章节传播来解释。图中的每个层下面的最后两行给像素(神经元)和重量,在整个层的总数目。
采用CNN训练的全职保存在一个txt或者xml文件中。(4)字符识别:对输入的单个字符,采用3.2中预处理,然后输入到3.3中的卷积核,通过BP达到一个结果就是该字符的识别结果。
#include <opencv/highgui.h> #include <opencv/cv.h> #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> #include <stdlib.h> #include <stdio.h> #include <time.h> /*用到了time函数,所以要有这个头文件*/ #include <fstream> #include <sstream> #include <exception> #include <vector> #include <io.h> #include <stdio.h> #include <math.h> #include <omp.h> #include <windows.h> #include "CNN.h" #include "global.h" #include "mnist.h" using namespace cv; using namespace std; int numLine1 = 0; //第一行字符个数 int numLine2 = 0; //第二行字符个数 #define showSteps 1 #define saveImage 1 int an =1; int element_shape = CV_SHAPE_RECT; IplConvKernel* element = cvCreateStructuringElementEx( an*2+1, an*2+1, an, an, element_shape, 0 ); LARGE_INTEGER m_liPerfFreq; LARGE_INTEGER m_liPerfStart; LARGE_INTEGER liPerfNow; double dfTim; void getStartTime() { QueryPerformanceFrequency(&m_liPerfFreq); QueryPerformanceCounter(&m_liPerfStart); } void getEndTime() { QueryPerformanceCounter(&liPerfNow); dfTim=( ((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000.0f)/m_liPerfFreq.QuadPart); } //config.txt为要处理的跟目录,在训练model中,config.txt中的内容为训练是的样本库,1-13分类好了。-- //在样本筛选model中,config.txt里的内容为要筛选的样本库,未分类。--- char * configFile = "config.txt"; //读取config文件里的内容-- char* trainSetPosPath = (char *)malloc(200*sizeof(char)); void readConfig(char* configFile, char* trainSetPosPath){ fstream f; char cstring[1000]; int readS=0; f.open(configFile, fstream::in); char param1[200]; strcpy(param1,""); char param2[200]; strcpy(param2,""); char param3[200]; strcpy(param3,""); //--读取第一行:-- f.getline(cstring, sizeof(cstring)); readS=sscanf (cstring, "%s %s %s", param1,param2, param3); strcpy(trainSetPosPath,param3); //--读取第二行:-- 字符个数 f.getline(cstring, sizeof(cstring)); readS=sscanf (cstring, "%s %s %i", param1,param2, &numLine1); //--读取第三行:-- 字符个数 f.getline(cstring, sizeof(cstring)); readS=sscanf (cstring, "%s %s %i", param1,param2, &numLine2); } //遍历config.txt里的根目录下的所有的文件,包括子目录。-- // 其中子目录的名字就是label,子目录里的文件为label对于的训练测试样本--- vector<string> imgNames; int labelTemp = 0; void dfsFolder(string folderPath){ _finddata_t FileInfo; string strfind = folderPath + "\\*"; long Handle = _findfirst(strfind.c_str(), &FileInfo); if (Handle == -1L) { cerr << "can not match the folder path" << endl; exit(-1); } do{ //判断是否有子目录-- if (FileInfo.attrib & _A_SUBDIR) { // cout<<FileInfo.name<<" "<<FileInfo.attrib<<endl; //这个语句很重要-- if( (strcmp(FileInfo.name,".") != 0 ) &&(strcmp(FileInfo.name,"..") != 0)) { string newPath = folderPath + "\\" + FileInfo.name; cout<<FileInfo.name<<" "<<newPath<<endl; //根目录下下的子目录名字就是label名,如果没有子目录则其为根目录下 labelTemp = atoi(FileInfo.name); // printf("%d\n",labelTemp); dfsFolder(newPath); } }else { string finalName = folderPath + "\\" + FileInfo.name; //将所有的文件名写入一个txt文件-- // cout << FileInfo.name << "\t"; // printf("%d\t",label); // cout << folderPath << "\\" << FileInfo.name << " " <<endl; //将文件名字和label名字(子目录名字赋值给向量)-- imgNames.push_back(finalName); } }while (_findnext(Handle, &FileInfo) == 0); _findclose(Handle); } void initTrainImage(){ readConfig(configFile, trainSetPosPath); string folderPath = trainSetPosPath; dfsFolder(folderPath); } //CNN识别初始化部分 void init(){ m_bTestSetOpen = m_bTrainingSetOpen = false; m_bStatusTraining = false; m_bStatusTesting = false; m_iCountTotal = 0; m_iCountError = 0; m_dDispX = new double[g_cVectorSize * g_cVectorSize]; m_dDispY = new double[g_cVectorSize * g_cVectorSize]; m_etaLearningRate = 0.00005; } void Calculate(IplImage *img){ Also create a color image (for display) //IplImage *colorimg = cvCreateImage( inputsz, IPL_DEPTH_8U, 3 ); //get image data int index = 0; for(int i = 0; i < img->height; ++i){ for(int j = 0; j < img->width; ++j){ index = i*img->width + j; grayArray[index] = ((uchar*)(img->imageData + i*img->widthStep))[j] ; //为像素赋值 } } int ii, jj; //copy gray scale image to a double input vector in -1 to 1 range // one is white, -one is black for ( ii=0; ii<g_cVectorSize * g_cVectorSize; ++ii ) inputVector[ii] = 1.0; for ( ii=0; ii<g_cImageSize; ++ii ) { for ( jj=0; jj<g_cImageSize; ++jj ) { int idxVector = 1 + jj + g_cVectorSize * (1 + ii); int idxImage = jj + g_cImageSize * ii; inputVector[ idxVector ] = double(255 - grayArray[ idxImage ])/128.0 - 1.0; } } //call forward propagation function of CNN m_iOutput = m_cnn.Calculate(inputVector, outputVector); } uchar lut[256]; CvMat* lut_mat; IplImage * dehist(IplImage * src_image, int _brightness, int _contrast){ IplImage *dst_image = cvCreateImage(cvGetSize(src_image),8,src_image->nChannels); cvCopy(src_image,dst_image); int brightness = _brightness - 100; int contrast = _contrast - 100; int i; float max_value = 0; if( contrast > 0 ) { double delta = 127.*contrast/100; double a = 255./(255. - delta*2); double b = a*(brightness - delta); for( i = 0; i < 256; i++ ) { int v = cvRound(a*i + b); if( v < 0 ) v = 0; if( v > 255 ) v = 255; lut[i] = (uchar)v; } } else { double delta = -128.*contrast/100; double a = (256.-delta*2)/255.; double b = a*brightness + delta; for( i = 0; i < 256; i++ ) { int v = cvRound(a*i + b); if( v < 0 ) v = 0; if( v > 255 ) v = 255; lut[i] = (uchar)v; } } lut_mat = cvCreateMatHeader( 1, 256, CV_8UC1 ); cvSetData( lut_mat, lut, 0 ); cvLUT( src_image, dst_image, lut_mat ); if(showSteps) cvShowImage( "dst_image", dst_image ); IplImage* gray_image = cvCreateImage(cvGetSize(src_image),8,1); cvCvtColor(dst_image, gray_image, CV_BGR2GRAY); IplImage* bin_image = cvCreateImage(cvGetSize(src_image),8,1); // int blockSize = 11; // int constValue = 9; // cvAdaptiveThreshold(gray_image, bin_image, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSize, constValue); cvThreshold(gray_image,bin_image,1,255,CV_THRESH_BINARY+CV_THRESH_OTSU); if(showSteps) cvShowImage( "bin_image", bin_image ); // OpenClose(bin_image,bin_image,-1); //cvErode(bin_image,bin_image,element,1); //cvDilate(bin_image,bin_image,element,8); cvReleaseImage(&dst_image); cvReleaseImage(&gray_image); return bin_image; } IplImage * projectY(IplImage * src){ IplImage *imgBin = cvCreateImage(cvGetSize(src),8,src->nChannels); cvCopy(src,imgBin); cvNot(src,imgBin); //Y轴投影 确定字符具体区域 IplImage* painty=cvCreateImage( cvGetSize(imgBin),IPL_DEPTH_8U, 1 ); cvZero(painty); int* h=new int[imgBin->height]; memset(h,0,imgBin->height*4); int x,y; CvScalar s,t; for(y=0;y<imgBin->height;y++) { for(x=0;x<imgBin->width;x++) { s=cvGet2D(imgBin,y,x); if(s.val[0]==0) h[y]++; } } //将y投影后,值小于50的赋值为0 for(y=0;y<imgBin->height;y++) { if((imgBin->width-h[y]) <= 13) h[y] = imgBin->width; // printf("%d ",imgBin->width-h[y]); } //将Y轴上 很窄的线段,即横着的很长的细线直接抹掉 for(x=0;x<painty->height;x++) { for(y=x;y<painty->height;y++) { if( (h[x] == h[y])&&(h[y] == painty->width)&&(y-x <= 3) ){ for(int i=x;i<=y;i++){ h[i] = painty->width; } } if( (h[x] != painty->width)&&(h[y] == painty->width)&&(y-x <= 3) ){ for(int i=x;i<=y;i++){ h[i] = painty->width; } } } } for(y=0;y<imgBin->height;y++) { for(x=0;x<h[y];x++) { t.val[0]=255; cvSet2D(painty,y,x,t); } } //确定Y轴字符的收尾,确定字符区域的高度 //查找paintx首尾两端的x轴坐标 int xLeft = 0; for(x=0;x<painty->height-2;x++){ if ( cvGet2D(painty,x,painty->width - 1).val[0]== 0 ){ xLeft = x; break; } if( (cvGet2D(painty,x,painty->width - 1).val[0] == 255)&&(cvGet2D(painty,x+1,painty->width - 1).val[0] == 0) ){ xLeft = x; break; } } if(showSteps) cout<<"列字符区域上边字符起始点是:"<<xLeft<<endl; int xRight = 0; for(x=painty->height-1; x>0 ;x--){ if ( cvGet2D(painty,x,painty->width - 1).val[0]== 0 ){ xRight = x; break; } if( (cvGet2D(painty,x,painty->width - 1).val[0]== 255)&&(cvGet2D(painty,x-1,painty->width - 1).val[0] == 0) ){ xRight = x; break; } } if(xRight == 0) xRight = painty->height; if(showSteps) cout<<"列字符区域下边字符起始点是:"<<xRight<<endl; if(showSteps){ cvNamedWindow("水平积分投影",1); cvShowImage("水平积分投影",painty); } IplImage * image = cvCreateImage(cvSize((src->width),xRight - xLeft + 4),8,src->nChannels); CvRect rect; rect.height = xRight - xLeft+4 ; rect.width = src->width; rect.x = 0; rect.y = xLeft-2; cvSetImageROI(src,rect); cvCopy(src,image); cvResetImageROI(src); cvReleaseImage(&imgBin); // cvReleaseImage(&src); cvReleaseImage(&painty); cvNamedWindow("image1",1); cvShowImage("image1",image); return image; } IplImage* projectX(IplImage * src){ // cvSmooth(src,src,CV_BLUR,3,3,0,0); IplImage * srcTemp = cvCreateImage(cvGetSize(src),8,src->nChannels); cvCopy(src,srcTemp); cvThreshold(src,src,50,255,CV_THRESH_BINARY_INV+CV_THRESH_OTSU); IplImage* paintx=cvCreateImage(cvGetSize(src),IPL_DEPTH_8U, 1 ); cvZero(paintx); int* v=new int[src->width]; int tempx=src->height; // CvPoint xPoint; int tempy=src->width; memset(v,0,src->width*4); int x,y; CvScalar s,t; for(x=0;x<src->width;x++){ for(y=0;y<src->height;y++){ s=cvGet2D(src,y,x); if(s.val[0]==0) v[x]++; } } //将x投影后,值小于2的赋值为0 for(x=0;x<src->width;x++) { if((src->height-v[x]) <= 3) v[x] = src->height; // printf("%d ",src->height-v[x]); } //若投影后出现很细的竖直黑线,将这条黑线去掉 for(x=0;x<paintx->width;x++) { for(y=x;y<paintx->width;y++) { if( (v[x] == v[y])&&(v[y] == paintx->height)&&(y-x < 3) ){ for(int i=x;i<=y;i++){ v[i] = paintx->height; } } if( (v[x] != paintx->width)&&(v[y] == paintx->width)&&(y-x <= 3) ){ for(int i=x;i<=y;i++){ v[i] = paintx->height; } } } } for(x=0;x<src->width;x++){ for(y=0;y<v[x];y++){ t.val[0]=255; cvSet2D(paintx,y,x,t); } } //查找paintx首尾两端的x轴坐标 int xLeft = 0; for(x=0;x<paintx->width;x++){ if( (cvGet2D(paintx,paintx->height - 3,x).val[0] == 255)&&(cvGet2D(paintx,paintx->height - 3,x+1).val[0] == 0) ){ xLeft = x; break; } } if(showSteps) cout<<"行字符区域左边字符起始点是:"<<xLeft<<endl; int xRight = 0; for(x=paintx->width-1; x>0 ;x--){ if( (cvGet2D(paintx,paintx->height - 1,x).val[0]== 255)&&(cvGet2D(paintx,paintx->height - 1,x-1).val[0] == 0) ){ xRight = x; break; } } if(showSteps) cout<<"行字符区域左边字符起始点是:"<<xRight<<endl; if(showSteps){ cvNamedWindow("二值图像",1); cvNamedWindow("垂直积分投影",1); cvShowImage("二值图像",src); cvShowImage("垂直积分投影",paintx); } IplImage * image = cvCreateImage(cvSize((xRight - xLeft),src->height),8,src->nChannels); CvRect rect; rect.height = src->height; rect.width = xRight - xLeft; rect.x = xLeft; rect.y = 0; cvSetImageROI(srcTemp,rect); cvCopy(srcTemp,image); cvResetImageROI(srcTemp); cvReleaseImage(&src); cvReleaseImage(&srcTemp); cvReleaseImage(&paintx); //cvNamedWindow("image",1); //cvShowImage("image",image); //cvWaitKey(0); return image; } char recVifcode(IplImage *src){ // Grayscale img pointer if (!src){ cout << "ERROR: Bad image file: " << endl; // continue; } CvSize inputsz = cvSize(28,28); // Grayscale img pointer IplImage* img = cvCreateImage( inputsz, IPL_DEPTH_8U, 1 ); // create some GUI if(showSteps){ cvNamedWindow("Image", CV_WINDOW_AUTOSIZE); cvMoveWindow("Image", inputsz.height, inputsz.width); } cvResize(src,img,CV_INTER_CUBIC); Calculate(img); if(showSteps){ cvShowImage("Image",img); } /*IplImage* imgDistorted = cvCreateImage( inputsz, IPL_DEPTH_8U, 1 ); cvCopy(img,imgDistorted); for(int i = 0; i < imgDistorted->height; ++i){ for(int j = 0; j < imgDistorted->width; ++j){ ((uchar*)(imgDistorted->imageData + i*imgDistorted->widthStep))[j] = (unsigned char) int (255 - 255 * (inputVector[(i+1)*g_cVectorSize + j + 1] + 1)/2); } } if(showSteps){ cvNamedWindow("imgDistorted", CV_WINDOW_AUTOSIZE); cvShowImage("imgDistorted",imgDistorted); }*/ // cout<<"识别结果为:"<<m_iOutput<<endl; char outputChar = NULL; if(m_iOutput == 0) outputChar = '0'; if(m_iOutput == 1) outputChar = '1'; if(m_iOutput == 2) outputChar = '2'; if(m_iOutput == 3) outputChar = '3'; if(m_iOutput == 4) outputChar = '4'; if(m_iOutput == 5) outputChar = '5'; if(m_iOutput == 6) outputChar = '6'; if(m_iOutput == 7) outputChar = '7'; if(m_iOutput == 8) outputChar = '8'; if(m_iOutput == 9) outputChar = '9'; if(m_iOutput == 10) outputChar = 'A'; if(m_iOutput == 11) outputChar = 'B'; if(m_iOutput == 12) outputChar = 'U'; if(m_iOutput == 13) outputChar = 'D'; if(m_iOutput == 14) outputChar = 'P'; if(m_iOutput == 15) outputChar = 'R'; if(m_iOutput == 16) outputChar = 'S'; if(m_iOutput == 17) outputChar = 'T'; if(m_iOutput == 18) outputChar = 'U'; if(m_iOutput == 19) outputChar = 'X'; if(m_iOutput == 20) outputChar = 'Y'; // cout<<"识别结果为:"<<outputChar<<endl; return outputChar; } void saveImages(IplImage * image){ SYSTEMTIME stTime; GetLocalTime(&stTime); char pVideoName[256]; sprintf(pVideoName, ".\\%d_%d_%d_%d_%d_%d_%d", stTime.wYear, stTime.wMonth, stTime.wDay, stTime.wHour, stTime.wMinute, stTime.wSecond, stTime.wMilliseconds); char image_name[500] ; sprintf_s(image_name,500, "%s%s%s", "result\\", pVideoName, ".bmp");//保存的图片名 cvSaveImage(image_name, image); } vector<char> segChar(IplImage * src,int x){ vector<char>imageRecLine1; //保存第一行的识别结果 vector<char>imageRecLine2;//保存第二行的识别结果 if((x!=1)&&(x!=2)){ printf("error! 必须指定是第几行!"); // return NULL; } // cvErode(src,src,element,1); if(showSteps) cvShowImage( "src", src ); //想垂直方向投影,确定字符区域的首尾,用于分割出单个字符 IplImage * image = projectX(src); if(x == 1){//第一行 //================判断第一个字符是否为 1 ===========================/ CvRect rectFirst; rectFirst.height = image->height; rectFirst.width = image->width/numLine1; rectFirst.x = 0; rectFirst.y = 0; IplImage * imageRoiFirst = cvCreateImage(cvSize(rectFirst.width,rectFirst.height),8,image->nChannels); cvSetImageROI(image,rectFirst); cvCopy(image,imageRoiFirst); cvResetImageROI(image); if(saveImage) saveImages(imageRoiFirst); char recResultFirst = recVifcode(imageRoiFirst); if(recResultFirst == '1'){ /*cout<<"wwwwwwwwwwwwwwwwwwwwwwwww"<<endl;*/ imageRecLine2.push_back(recResultFirst);//保存第二行的第一个字符1的识别结果 const int firstLen = 20; for(int i=0;i<numLine1-1;i++){ CvRect rect; rect.height = image->height; rect.width = (image->width-firstLen)/(numLine1-1); rect.x = i*(image->width-firstLen)/(numLine1-1) + firstLen; rect.y = 0; IplImage * imageRoi = cvCreateImage(cvSize(rect.width,rect.height),8,image->nChannels); cvSetImageROI(image,rect); cvCopy(image,imageRoi); cvResetImageROI(image); cvShowImage("1",imageRoi); cvWaitKey(0); if(saveImage) saveImages(imageRoi); //调用识别部分代码 char recResult = recVifcode(imageRoi); imageRecLine2.push_back(recResult);//保存第二行的识别结果 } }else{ for(int i=0;i<numLine1;i++){ CvRect rect; rect.height = image->height; rect.width = image->width/numLine1; rect.x = i*image->width/numLine1; rect.y = 0; IplImage * imageRoi = cvCreateImage(cvSize(rect.width,rect.height),8,image->nChannels); cvSetImageROI(image,rect); cvCopy(image,imageRoi); cvResetImageROI(image); cvShowImage("1",imageRoi); cvWaitKey(0); if(saveImage) saveImages(imageRoi); //调用识别部分代码 char recResult = recVifcode(imageRoi); imageRecLine1.push_back(recResult);//保存第二行的识别结果 } } return imageRecLine1; } if(x == 2){//第一行 //================判断第一个字符是否为 1 ===========================/ CvRect rectFirst; rectFirst.height = image->height; rectFirst.width = image->width/numLine2; rectFirst.x = 0; rectFirst.y = 0; IplImage * imageRoiFirst = cvCreateImage(cvSize(rectFirst.width,rectFirst.height),8,image->nChannels); cvSetImageROI(image,rectFirst); cvCopy(image,imageRoiFirst); cvResetImageROI(image); if(saveImage) saveImages(imageRoiFirst); char recResultFirst = recVifcode(imageRoiFirst); if(recResultFirst == '1'){ /*cout<<"wwwwwwwwwwwwwwwwwwwwwwwww"<<endl;*/ imageRecLine2.push_back(recResultFirst);//保存第二行的第一个字符1的识别结果 const int firstLen = 20; for(int i=0;i<numLine2-1;i++){ CvRect rect; rect.height = image->height; rect.width = (image->width-firstLen)/(numLine2-1); rect.x = i*(image->width-firstLen)/(numLine2-1) + firstLen; rect.y = 0; IplImage * imageRoi = cvCreateImage(cvSize(rect.width,rect.height),8,image->nChannels); cvSetImageROI(image,rect); cvCopy(image,imageRoi); cvResetImageROI(image); cvShowImage("1",imageRoi); cvWaitKey(0); if(saveImage) saveImages(imageRoi); //调用识别部分代码 char recResult = recVifcode(imageRoi); imageRecLine2.push_back(recResult);//保存第二行的识别结果 } }else{ for(int i=0;i<numLine2;i++){ CvRect rect; rect.height = image->height; rect.width = image->width/numLine2; rect.x = i*image->width/numLine2; rect.y = 0; IplImage * imageRoi = cvCreateImage(cvSize(rect.width,rect.height),8,image->nChannels); cvSetImageROI(image,rect); cvCopy(image,imageRoi); cvResetImageROI(image); cvShowImage("1",imageRoi); cvWaitKey(0); if(saveImage) saveImages(imageRoi); //调用识别部分代码 char recResult = recVifcode(imageRoi); imageRecLine2.push_back(recResult);//保存第二行的识别结果 } } return imageRecLine2; } } void printResult(vector<char> imageRecLine){ int recNum = imageRecLine.size(); for(int iNum=0;iNum<recNum;iNum++){ cout<<imageRecLine[iNum]; } cout<<endl; } void processingTotal(){ initTrainImage(); char * weightFileName = "Weight.txt"; m_cnn.LoadWeights(weightFileName); int imgNum = imgNames.size(); for(int iNum=0;iNum<imgNum;iNum++){ cout<<endl<<iNum<<endl; cout<<imgNames[iNum].c_str()<<endl; IplImage * src=cvLoadImage(imgNames[iNum].c_str(),1); if(!src) continue; // // // Mat image(src); // Mat dst = image.clone(); for ( int i = 1; i < 480; i = i + 2 ){ // int i = 30; // bilateralFilter ( image, dst, i, i*2, i/2 ); // imshow( "image", image ); // imshow( "dst", dst ); // waitKey ( 0 ); } // /// getStartTime(); IplImage * tgray = cvCreateImage(cvGetSize(src),8,1); cvCvtColor(src,tgray,CV_BGR2GRAY); IplImage * bin_image = cvCreateImage(cvGetSize(src),8,1); bin_image = dehist(src,83,134); cvErode(bin_image,bin_image,element,12); if(showSteps) cvShowImage( "bin_image1", bin_image ); CvMemStorage *mems=cvCreateMemStorage(); CvSeq *contours; cvFindContours( bin_image, mems, &contours,sizeof(CvContour),CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); // cvDrawContours(src, contours, CV_RGB(0,0,255), CV_RGB(255, 0, 0), 2, 2, 8, cvPoint(0,0)); cvClearMemStorage( mems ); if(showSteps){ cvNamedWindow("cvDrawContours",1); cvShowImage("cvDrawContours",src); } CvRect rect; // char image_name[100]; CvSeq* first_seq = contours; // int image_num = 0; for( contours=first_seq; contours != 0; contours = contours->h_next ){ rect = cvBoundingRect(contours); cout<<rect.x<<" "<<rect.y<<" "<<rect.width<<" "<<rect.height<<endl; //根据轮廓大小信息做筛选 if( (rect.width > 440)||(rect.height > 300)|| // (rect.width/rect.height > 5)|| (rect.width*rect.height < 4500)|| (rect.width < 100)||(rect.height < 50)|| (rect.x < 0)||(rect.y < 0)|| (rect.x > src->width)||(rect.y > src->height) ) continue; else{ CvPoint pt1;pt1.x = rect.x; pt1.y = rect.y; CvPoint pt2;pt2.x = rect.x + rect.width; pt2.y = rect.y + rect.height; if(showSteps){ cvRectangle(src,pt1,pt2,CV_RGB(255,0,255),2/*CV_FILLED*/,CV_AA,0); cout<<rect.x<<" "<<rect.y<<" "<<rect.width<<" "<<rect.height<<endl; } if(showSteps){ cvNamedWindow("cvDrawContours",1); cvShowImage("cvDrawContours",src); } //扣出字符区域后,在对小图的字符区域做二值化,滤波,然后roi,旋转矫正?????????? CvRect rectTemple; rectTemple.x = rect.x - 5; rectTemple.y = rect.y - 5; rectTemple.width = rect.width + 10; rectTemple.height = rect.height + 10; IplImage* tempImage = cvCreateImage(cvSize(rectTemple.width,rectTemple.height),8,1); cvSetImageROI(tgray,rectTemple); cvCopy(tgray,tempImage); cvResetImageROI(tgray); cvThreshold(tempImage,tempImage,1,255,CV_THRESH_BINARY+CV_THRESH_OTSU); if(showSteps){ cvNamedWindow("tempImage",1); cvShowImage("tempImage",tempImage); } CvBox2D box_outer = cvMinAreaRect2(contours); // cout<<box_outer.center.x<<" "<<box_outer.center.y<<" "<<box_outer.angle<<" "<<box_outer.size.width<<" "<<box_outer.size.height<<endl; //旋转矫正tempImage IplImage* tempImageRotate = cvCreateImage(cvGetSize(tempImage),8,1); float m[6]; // Matrix m looks like: // // [ m0 m1 m2 ] ===> [ A11 A12 b1 ] // [ m3 m4 m5 ] [ A21 A22 b2 ] // CvMat M = cvMat (2, 3, CV_32F, m); int w = tempImage->width; int h = tempImage->height; if( (box_outer.angle+90.)<45 ){ // 仅仅旋转 int factor = 1; m[0] = (float) (factor * cos ((-1)*(box_outer.angle+90.)/2. * 2 * CV_PI / 180.)); m[1] = (float) (factor * sin ((-1)*(box_outer.angle+90.)/2. * 2 * CV_PI / 180.)); m[3] = (-1)*m[1]; m[4] = m[0]; // 将旋转中心移至图像中间 m[2] = w * 0.5f; m[5] = h * 0.5f; // dst(x,y) = A * src(x,y) + b } if( (box_outer.angle+90.)>45 ){ // 仅仅旋转 int factor = 1; m[0] = (float) (factor * cos ((-1)*(box_outer.angle)/2. * 2 * CV_PI / 180.)); m[1] = (float) (factor * sin ((-1)*(box_outer.angle)/2. * 2 * CV_PI / 180.)); m[3] = (-1)*m[1]; m[4] = m[0]; // 将旋转中心移至图像中间 m[2] = w * 0.5f; m[5] = h * 0.5f; // dst(x,y) = A * src(x,y) + b } cvZero (tempImageRotate); cvGetQuadrangleSubPix (tempImage, tempImageRotate, &M); if(showSteps){ cvNamedWindow("ROI",1); cvShowImage("ROI",tempImageRotate); } cvReleaseImage( &tempImage ); //====================对tempImageRotate做Y轴投影===================// IplImage* imageCharOk = projectY(tempImageRotate); //直接上下 均分 分割出上下两行 CvRect rectTop; rectTop.x = 0; rectTop.y = 0; rectTop.width = imageCharOk->width ; rectTop.height = imageCharOk->height/2; IplImage* imageCharOkTop = cvCreateImage(cvSize(rectTop.width,rectTop.height),8,1); cvSetImageROI(imageCharOk,rectTop); cvCopy(imageCharOk,imageCharOkTop); cvResetImageROI(imageCharOk); if(showSteps){ cvNamedWindow("imageCharOkTop",1); cvShowImage("imageCharOkTop",imageCharOkTop); } vector<char> results = segChar(imageCharOkTop,1); printResult(results); CvRect rectBottom; rectBottom.x = 0; rectBottom.y = 0 + imageCharOk->height/2; rectBottom.width = imageCharOk->width ; rectBottom.height = imageCharOk->height/2; IplImage* imageCharOkBottom = cvCreateImage(cvSize(rectBottom.width,rectBottom.height),8,1); cvSetImageROI(imageCharOk,rectBottom); cvCopy(imageCharOk,imageCharOkBottom); cvResetImageROI(imageCharOk); if(showSteps){ cvNamedWindow("imageCharOkBottom",1); cvShowImage("imageCharOkBottom",imageCharOkBottom); } vector<char> results2 = segChar(imageCharOkBottom,2); printResult(results2); } } getEndTime(); printf("%f 毫秒\n",dfTim); cvWaitKey(0); cvReleaseImage( &src ); cvReleaseImage( &tgray ); cvReleaseImage( &bin_image ); } } void main(){ init(); processingTotal(); }
-
halcon字符识别(QT调用halcon库实现字符识别(OCR识别))
2019-10-22 16:52:57由于项目中要做字符识别(OCR),因为halcon算法丰富,算法效率高,开发起来方便省时。所以选择了halcon库为项目的图像处理基础库。 2、halcon字符识别用到的类。 (1)、HImage类:halcon的图像数据存储类。 (2)...1、简介
halcon是一款和图像处理库,里面的算法很多,效率很高。由于项目中要做字符识别(OCR),因为halcon算法丰富,算法效率高,开发起来方便省时。所以选择了halcon库为项目的图像处理基础库。2、halcon字符识别用到的类。
(1)、HImage类:halcon的图像数据存储类。
(2)、HTextModel类:halcon字符识别算子类。
(3)、HTextResult类:字符识别返回的结果存储类。
(3)、HTuple类:halcon存储基本数据的类。类似C++的int。
(4)、HRegion类:halcon用来表示区域的类,类似QT的Item类。
3、源码演示
//创建字符识别算子对象
HTextModel* MainWindow::createTextModel(QString sFontName)
{
HTextModel* pTextModel = NULL;//2. 创建字库对象,加载已经训练好的字库数据
QString sPath = "/home/mark/Desktop/" + sFontName;
if (QDir(sPath).exists())
{
//自己训练的字库
pTextModel = new HTextModel("auto", sPath.toUtf8().constData());
}
else
{
//Haicon自己训练的字库
pTextModel = new HTextModel("auto", sFontName.toUtf8().constData());
}//3. 设置字符与背景对比度默认是15
int iMinContrast = ui->sbMinContrast->value();
HTuple hvContrast(iMinContrast == 0 ? 15 : iMinContrast);
pTextModel->SetTextModelParam(HTuple("min_contrast"), hvContrast);//4. 设置字符最小宽度
int iCharWidthMin = ui->sbCharWidthMin->value();
HTuple hvCharWidthMin(iCharWidthMin == 0 ? "auto" : HTuple(iCharWidthMin));
pTextModel->SetTextModelParam(HTuple("min_char_width"), hvCharWidthMin);//5. 设置字符最大宽度
int iCharWidthMax = ui->sbCharWidthMax->value();
HTuple hvCharWidthMax(iCharWidthMax == 0 ? "auto" : HTuple(iCharWidthMax));
pTextModel->SetTextModelParam(HTuple("max_char_width"), hvCharWidthMax);//6. 设置字符最小高度
int iCharHeightMin = ui->sbCharHeightMin->value();
HTuple hvCharHeightMin(iCharHeightMin == 0 ? "auto" : HTuple(iCharHeightMin));
pTextModel->SetTextModelParam(HTuple("min_char_height"), hvCharHeightMin);//7. 设置字符最大高度
int iCharHeightMax = ui->sbCharHeightMax->value();
HTuple hvCharHeightMax(iCharHeightMax == 0 ? "auto" : HTuple(iCharHeightMax));
pTextModel->SetTextModelParam(HTuple("max_char_height"), hvCharHeightMax);//8. 设置笔划最小宽度
int iStrokeWidthMin = ui->sbStrokeWidthMin->value();
HTuple hvStrokeWidthMin(iStrokeWidthMin == 0 ? "auto" : HTuple(iStrokeWidthMin));
pTextModel->SetTextModelParam(HTuple("min_stroke_width"), hvStrokeWidthMin);//9. 设置笔划最大宽度
int iStrokeWidthMax = ui->sbStrokeWidthMax->value();
HTuple hvStrokeWidthMax(iStrokeWidthMax == 0 ? "auto" : HTuple(iStrokeWidthMax));
pTextModel->SetTextModelParam(HTuple("max_stroke_width"), hvStrokeWidthMax);//10. 设置识别分隔符
HTuple hvSeparator(ui->cbSeparator->isChecked() ? "true" : "false");
pTextModel->SetTextModelParam(HTuple("return_separators"), hvSeparator);//11. 识别标点符号
HTuple hvPunctuation(ui->cbPunctuation->isChecked() ? "true" : "false");
pTextModel->SetTextModelParam(HTuple("return_punctuation"), hvPunctuation);return pTextModel;
}//字符识别
QString MainWindow::findText(const HImage &hImage, bool bAuto, QVector<TextInfo> &vTextInfo)
{
if (m_pTextModel == NULL || !m_pTextModel->IsHandleValid()) return "";//1. 返回的识别出来的字符
QString sRet = "";//2. 模式设置为“自动”的文本模型要求文本行至少包含两个字符
// 而模式设置为“手动”的文本模型要求文本行至少包含三个字符。
HTextResult hTextResult = m_pTextModel->FindText(hImage);//3. 获取找到的 字符分割
HObject hoCharacters = hTextResult.GetTextObject("all_lines");//4. 获取找到的 第1和第2阶梯的 字符/匹配度
HTuple hvClasses0 = hTextResult.GetTextResult((HTuple("class").Append(0)));
HTuple hvClasses1 = hTextResult.GetTextResult((HTuple("class").Append(1)));
HTuple hvConfidence = hTextResult.GetTextResult("confidence");int iLenght = hvClasses0.Length();
for (int i = 0; i < iLenght; i++) sRet += (QString)(hvClasses0[i].S());//5. 校正字符数字0 与 字母O
float fRatio = ui->sbRatioWH->value() == 0 ? 0.8f : ui->sbRatioWH->value();
if (fRatio > 1.0f) fRatio = 1.0f;
if (fRatio < 0.5f) fRatio = 0.5f;//6. 根据设置的字符宽高比,区别字符o 和 字符0
QString sCheck = "O0";
for (int i = 0; i < iLenght; i++)
{
QString s0 = QString(hvClasses0[i].S());
QString s1 = QString(hvClasses1[i].S());
HObject hoSelected = hoCharacters.SelectObj(i + 1);HTuple hvWidth, hvHeight;
RegionFeatures(hoSelected, "width", &hvWidth);
RegionFeatures(hoSelected, "height", &hvHeight);//取字符的最小外接矩形
TextInfo info;
int w = hvWidth.D();
int h = hvHeight.D();
info.sText = (QString)hvClasses0[i].S();
info.iWidth = w;
info.iHight = h;
info.fScore = hvConfidence[i].D();HTuple hvCol; //最小外接矩形中心坐标X
HTuple hvRow; //最小外接矩形中心坐标Y
HTuple hvPhi; //最小外接矩形的角度(弧度表示)
HTuple hvHalfW; //矩形宽度的一半 h/2
HTuple hvHalfH; //矩形高度的一半 w/2//通过条码的矩形对象 获取矩形的 中心点、角度、宽高
SmallestRectangle2(hoSelected, &hvRow, &hvCol, &hvPhi, &hvHalfW, &hvHalfH);Hlong lCol = hvCol.D();
Hlong lRow = hvRow.D();
double lPhi = hvPhi.D();
Hlong lLength1 = hvHalfW.D();
Hlong lLength2 = hvHalfH.D();
info.rect = QRotatedRect(lCol, lRow, lLength1 * 2, lLength2 * 2, -lPhi * M_180_PI);vTextInfo.push_back(info);
if (!sCheck.contains(s0, Qt::CaseInsensitive) || !sCheck.contains(s1, Qt::CaseInsensitive)) continue;
float fCurrent = hvWidth.D() / hvHeight.D();
if (fCurrent <= fRatio && s0.contains("O", Qt::CaseInsensitive)) sRet.replace(i, 1, "0");
if (fCurrent > fRatio && s0.contains("0", Qt::CaseInsensitive)) sRet.replace(i, 1, "O");
}//7. 如果是自动识别模式下,自动修改识别字符的参数,最后更新到界面
if (bAuto && iLenght > 0)
{
//统计字符的宽度、高度
int iWidthMin = -100;
int iWidthMax = -100;
int iHeightMin = -100;
int iHeightMax = -100;
for (int i = 0; i < iLenght; i++)
{
//忽略标点符号
if (!sRet.at(i).isLetterOrNumber()) continue;HObject hoSelected = hoCharacters.SelectObj(i + 1);
//获取字符的宽度和高度(自动模式)
HTuple hvWidth, hvHeight;
RegionFeatures(hoSelected, "width", &hvWidth);
RegionFeatures(hoSelected, "height", &hvHeight);
int w = hvWidth.D();
int h = hvHeight.D();if (i == 0)
{
iWidthMin = w;
iWidthMax = w;
iHeightMin = h;
iHeightMax = h;
}
else
{
if (iWidthMin > w) iWidthMin = w;
if (iWidthMax < w) iWidthMax = w;
if (iHeightMin > h) iHeightMin = h;
if (iHeightMax < h) iHeightMax = h;
}
}int iOffset = 3;
iWidthMin = iWidthMin - iOffset;
iWidthMax = iWidthMax + iOffset;
iHeightMin = iHeightMin - iOffset;
iHeightMax = iHeightMax + iOffset;ui->sbCharWidthMin->setValue(iWidthMin > 0 ? iWidthMin : 0);
ui->sbCharWidthMax->setValue(iWidthMax > 0 ? iWidthMax : 0);
ui->sbCharHeightMin->setValue(iHeightMin > 0 ? iHeightMin : 0);
ui->sbCharHeightMax->setValue(iHeightMax > 0 ? iHeightMax : 0);
}//8. 释放内存
if (hTextResult.IsHandleValid()) hTextResult.Clear();return sRet;
}4、测试结果
-
图像识别之字符识别方法
2020-05-20 23:19:29数据来源于天池赛题:零基础入门CV之街道字符识别 地址:https://tianchi.aliyun.com/competition/entrance/531795/introduction 对于不定长和不规则的字符而言,有多种CV方法可以对其进行识别,下面将做一个具体的... -
opencv 车牌 字符识别
2015-02-13 13:42:50基于opencv实现的车牌号做字符识别 -
基于模板匹配的字符识别(Matlab) 字符识别模板匹配方法
2017-08-03 09:59:161 字符识别简介 字符识别是车牌识别中很重要的一部分,在模式识别中也扮演的很重要的角色。当然,我们可以用很多方法拉进行字符识别,比如:基于向量机(SVM),神经网络,小波等方法。当然基于模板匹配也是一种方法... -
Halcon 雕刻字符识别
2020-07-19 09:48:21Halcon 雕刻字符识别 -
手写字符识别
2018-05-14 22:56:11手写字符图像识别属于OCR中的一种,手写字符识别比书写字符识别的难度要大得多,即使现在采用深度学习,百度和阿里对手写的效果都不是很好,无法达到现实场景实用的水平。 个人认为,主要原因在于数据的收集较为... -
OCR字符识别
2017-04-01 18:52:38OCR字符识别步骤: -
车牌识别 字符识别
2013-06-27 09:02:54摘要:提出了一种完全基于结构知识的字符识别方法。该方法以字符的结构特点和笔画类型、数据及位置作为识别特片生成判定时,再利用判定树对汽车牌照中的字母和数字进行分类识别。 关键词:车牌号码 字符结构 ... -
基于模板匹配的字符识别
2014-06-24 15:28:08本文主要要实现字符识别,识别方法是用模板匹配。内容包含模板,待识别字符,完整程序。希望大家能够帮助大家。 -
字符识别OCR研究三 字符识别,字符区域定位 经验总结:
2012-02-22 21:41:51字符识别经验总结: 一、 视频帧中字符的识别(video ocr): 难点1:视频流中,出现字符后,立即开始采集含有字符的视频帧; 难点2:对视频帧中字符区域的定位; 难点3:快速有效的识别出字符; 其中视频帧中... -
快速字符识别算法
2016-12-05 19:29:28快速字符识别 -
halconOCR字符识别测试
2020-02-15 17:58:07halconOCR字符识别测试 对比visionpro,感觉halcon更方便一下,读取效果也不错。 主要是要分割好(首选自动文本分割)。 自带字符库可以满足大部分项目需求。 ... -
OCR字符识别检测
2018-06-12 16:22:32一、字符识别系统描述 本系统用于电子秤出厂前校验过程中显示重量数字的读取及发送功能,系统采用进口工业相机,可以快速获取产品图像,通过图像识别、分析和计算,给出电子秤显示的重量数值,也可根据误差范围进行... -
Halcon字符识别
2016-07-25 15:16:35今天突然想起来玩一下Halcon,在Halcon论坛上看到有一个帖子写的是字符识别的方法,当时看了看有些没明白就查了点资料,现在来分享一下: 1、Halcon的图像处理过程一般分为以下几个步骤 (1)图像读取 (2)图像... -
汉字识别OCR字符识别源代码
2009-10-31 09:33:03OCR字符识别源代码.. .. -
字符识别(字符串处理)
2017-08-18 15:13:13中南林业科技大学计算机与信息工程学院某位老师开发一个字符识别程序. 这个程序用来将纸质文档转换为电子版. 很显然, 这个转换过程不能保证100%正确, 其中有些字符不能识别. 你的任务是写一个程序帮助这位老师计算... -
基于opencv的手写数字字符识别
2013-11-29 17:36:00实现了基于opencv 的手写数字字符识别 主要参照文章: http://blog.damiles.com/2008/11/basic-ocr-in-opencv/ 基本上就是按着人家的代码来配置的,完后小改动了几个参数,写了一个文档,方便大家学习吧。 -
字符识别?
2014-12-21 21:35:18字符识别? Time Limit:1000MS Memory Limit:131072KB 64bit IO Format:%lld & %llu Submit Status Description 你的任务是写一个程序进行字符识别。别担心,你只需要识别1, 2, 3,如下: .*.... -
数字OCR字符识别与车牌识别
2015-04-03 09:11:59数字OCR字符识别与车牌识别 OCR:光学字符识别技术 所谓OCR (Optical Character Recognition光学字符识别)技术,是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定... -
基于matlab的蓝色车牌识别(车牌字符识别)
2019-07-17 21:09:28目录 1 处理流程 2 结果展示 3 核心要点解读 4 matlab代码 整套方案还包括以下博客: ...(1)基于matlab的蓝色车牌识别(绪论) ...(5)基于matlab的蓝色车牌识别(车牌字符识别) 转载请注明出处,谢谢... -
汽车车牌识别系统实现(四)--字符识别+代码实现
2020-11-03 21:08:00字符识别 一、字符识别算法的实现 车牌字符识别是车牌识别中的最后一步,也是决定车牌识别成功与否的关键步骤。字符识别是对经过车牌定位、车牌纠正和车牌字符划分后得到的各个车牌字符进行识别的过程。字符识别利用... -
OCR—光学字符识别
2019-03-26 10:44:15OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。 最近对... -
车牌字符识别算法原理
2017-11-28 21:50:17首先车牌字符识别算法原理是怎样的,车牌识别技术要求能够将运动中的汽车牌照从复杂背景中提取并识别出来,通过车牌提取、图像预处理、特征提取、车牌字符识别等技术,识别车辆牌号、颜色等信息,目前最新的技术水平... -
halcon 字符识别 交流
2018-11-05 20:47:21Halcon 字符识别 最近刚开始学习halcon,对这部分有点体会,在这给大家分享一波 一 图像处理基本步骤 在图像处理中,基本可以归纳为下面四个部分: 采集图像——预处理——图像分割——识别显示 二 采集图像 采集...