chars_segment.h用于从已经通过SVM判别得到的车牌区域中将车牌的字符分割开,用于下一步的ANN字符识别。

namespace easypr {

class CCharsSegment {
 public:
  CCharsSegment();
  // using ostu algotithm the segment chars in plate字符分割,步骤为:灰度化,阈值,找轮廓,外接矩形,从左到右排序,找城市字符,找汉字字符。详情见文末3,原理:https://blog.csdn.net/qq_30815237/article/details/88763027
  int charsSegment(Mat input, std::vector<Mat>& resultVec, Color color = BLUE);

//! using methods to segment chars in plate,charsSegmentUsingOSTU与charsSegment相比增添了一个去除铆钉的操作
 //charsSegmentUsingMSER使用MSER方法,代码较长,日后再详述。

  int charsSegmentUsingOSTU(Mat input, std::vector<Mat>& resultVec, std::vector<Mat>& grayChars, Color color = BLUE);
  int charsSegmentUsingMSER(Mat input, vector<Mat>& resultVec, vector<Mat>& grayChars, Color color = BLUE);

//大律阈值二值化,去铆钉clearLiuDing函数,调用ProjectedHistogram函数,都在core_func.cpp文件中,参见文末4,5,投影原理:参见    https://blog.csdn.net/qq_30815237/article/details/88665822
  int projectSegment(const Mat& input, Color color, vector<int>& out_indexs);

//参见文末1
  bool verifyCharSizes(Mat r);

// find the best chinese binaranzation method,之后详述,在CharsIdentify头文件中
  void judgeChinese(Mat in, Mat& out, Color plateType);
  void judgeChineseGray(Mat in, Mat& out, Color plateType);//还未定义,函数体:out=in

//首先进行仿射变换,将字符统一大小,并归一化到中间,并resize为 20*20,如下图所示:

转化为    
Mat preprocessChar(Mat in);

//找到汉字字符,传入参数是上一步操作中找的的城市字符的矩形框,返回汉字字符的矩形框
  Rect GetChineseRect(const Rect rectSpe);

//找到代表城市的字符, like "苏A" 的A,目的是为了下一步找到汉字字符,一般为特殊字符左移字符宽度的1.15倍;参见文末2
  int GetSpecificRect(const std::vector<Rect>& vecRect);
  //  从城市字符开始,从左到右取前5个字符,排除右边边界会出现误判的 I,得到:
  int RebuildRect(const std::vector<Rect>& vecRect, std::vector<Rect>& outRect,int specIndex);

//没有这个函数的定义,这个函数可能是想实现字符矩形框的排列,但实际使用sort函数就可以实现,不需要使用该函数
  int SortRect(const std::vector<Rect>& vecRect, std::vector<Rect>& out);
  inline void setLiuDingSize(int param) { m_LiuDingSize = param; }
  inline void setColorThreshold(int param) { m_ColorThreshold = param; }

inline void setBluePercent(float param) { m_BluePercent = param; }
  inline float getBluePercent() const { return m_BluePercent; }
  inline void setWhitePercent(float param) { m_WhitePercent = param; }
  inline float getWhitePercent() const { return m_WhitePercent; }

static const int DEFAULT_DEBUG = 1;
  static const int CHAR_SIZE = 20;
  static const int HORIZONTAL = 1;
  static const int VERTICAL = 0;

static const int DEFAULT_LIUDING_SIZE = 7;
  static const int DEFAULT_MAT_WIDTH = 136;
  static const int DEFAULT_COLORTHRESHOLD = 150;

inline void setDebug(int param) { m_debug = param; }
  inline int getDebug() { return m_debug; }

private:

int m_LiuDingSize;
 int m_theMatWidth;
 int m_ColorThreshold;
  float m_BluePercent;
  float m_WhitePercent;
  int m_debug;
};
}

1、判别分割得到的字符尺寸大小,从面积,长宽比和字符的宽度高度等角度进行字符校验。如果不满足尺寸,则可能不是字符或不是完整的字符(汉字),返回flase.

bool CCharsSegment::verifyCharSizes(Mat r) {// Char sizes 45x90float aspect = 45.0f / 90.0f;  //宽高比float charAspect = (float)r.cols / (float)r.rows;float error = 0.7f;float minHeight = 10.f;//车牌区域Mat的尺寸float maxHeight = 35.f;// We have a different aspect ratio for number 1, and it can be ~0.2字符“1”比较特殊float minAspect = 0.05f;float maxAspect = aspect + aspect * error;//最大最小范围// area of pixelsint area = cv::countNonZero(r);//对二值化图像执行countNonZero。可得到非零像素点数(字符像素).int bbArea = r.cols * r.rows;int percPixels = area / bbArea;//百分比if (percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect &&r.rows >= minHeight && r.rows < maxHeight)return true;elsereturn false;
}

2、寻找汉字字符后面的一个字符,根据它的位置先验信息(在整个车牌的1/7和2/7范围内)

int CCharsSegment::GetSpecificRect(const vector<Rect>& vecRect) {vector<int> xpositions;int maxHeight = 0;int maxWidth = 0;for (size_t i = 0; i < vecRect.size(); i++) {xpositions.push_back(vecRect[i].x);//将矩形框的左上角坐标存放在一起if (vecRect[i].height > maxHeight) {maxHeight = vecRect[i].height;}if (vecRect[i].width > maxWidth) {maxWidth = vecRect[i].width;}}int specIndex = 0;for (size_t i = 0; i < vecRect.size(); i++) {Rect mr = vecRect[i];int midx = mr.x + mr.width / 2;   //矩形框的中心点x坐标// find the specific character汉字后面紧跟着的字符,位置在整个车牌宽的 1/7 and 2/7之间if ((mr.width > maxWidth * 0.6 || mr.height > maxHeight * 0.6) &&(midx < int(m_theMatWidth / kPlateMaxSymbolCount) * kSymbolIndex &&midx > int(m_theMatWidth / kPlateMaxSymbolCount) * (kSymbolIndex - 1))) {specIndex = i;}}return specIndex;//返回特殊字符的索引
}

3、

int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color) {if (!input.data) return 0x01;Color plateType = color;Mat input_grey;cvtColor(input, input_grey, CV_BGR2GRAY);//转为灰度图Mat img_threshold;img_threshold = input_grey.clone();spatial_ostu(img_threshold, 8, 2, plateType);//自定义的空间大律法阈值// remove liuding and hor lines// also judge weather is plate use jump countif (!clearLiuDing(img_threshold)) return 0x02;Mat img_contours;img_threshold.copyTo(img_contours);vector<vector<Point> > contours;//找轮廓findContours(img_contours,contours,               // a vector of contoursCV_RETR_EXTERNAL,       // retrieve the external contoursCV_CHAIN_APPROX_NONE);  // all pixels of each contoursvector<vector<Point> >::iterator itc = contours.begin();vector<Rect> vecRect;while (itc != contours.end()) {Rect mr = boundingRect(Mat(*itc));Mat auxRoi(img_threshold, mr);if (verifyCharSizes(auxRoi)) vecRect.push_back(mr);//求单个字符轮廓的外接矩形,根据大小判断++itc;}if (vecRect.size() == 0) return 0x03;vector<Rect> sortedRect(vecRect);std::sort(sortedRect.begin(), sortedRect.end(),[](const Rect& r1, const Rect& r2) { return r1.x < r2.x; });//自定义排序方法,按x坐标升序排列,就是按车牌从左到右排列字符size_t specIndex = 0;specIndex = GetSpecificRect(sortedRect);//得到汉字字符后面的字符,主要是为了把汉字字符分开Rect chineseRect;if (specIndex < sortedRect.size())chineseRect = GetChineseRect(sortedRect[specIndex]);//得到汉字字符elsereturn 0x04;vector<Rect> newSortedRect;newSortedRect.push_back(chineseRect);RebuildRect(sortedRect, newSortedRect, specIndex);//除汉字字符外的字符按顺序排列存放在newSortedRect中if (newSortedRect.size() == 0) return 0x05;bool useSlideWindow = true;bool useAdapThreshold = true;for (size_t i = 0; i < newSortedRect.size(); i++) {Rect mr = newSortedRect[i];//使用拷贝构造函数Mat(constMat& m, const Rect& roi ),矩形roi指定了兴趣区Mat auxRoi(input_grey, mr);Mat newRoi;if (i == 0) {//i=0表示汉字字符,使用滑动框if (useSlideWindow) {float slideLengthRatio = 0.1f;//float slideLengthRatio = CParams::instance()->getParam1f();
//改进中文字符的识别,在识别中文时,增加一个小型的滑动窗口slideChineseWindow,以此弥补通过省份字符直接查找中文字符时的定位不精等现象;if (!slideChineseWindow(input_grey, mr, newRoi, plateType, slideLengthRatio, useAdapThreshold))judgeChinese(auxRoi, newRoi, plateType);}elsejudgeChinese(auxRoi, newRoi, plateType);}else {if (BLUE == plateType) {threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU);}else if (YELLOW == plateType) {threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU);}else if (WHITE == plateType) {threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);}else {threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);}newRoi = preprocessChar(newRoi);}if (i == 0) {imshow("newRoi", newRoi);waitKey(0);destroyWindow("newRoi");}}resultVec.push_back(newRoi);//最终分割完成的结果}return 0;
}

4、水平投影

Mat ProjectedHistogram(Mat img, int t, int threshold) {//参数t为0,对图像进行水平投影int sz = (t) ? img.rows : img.cols;Mat mhist = Mat::zeros(1, sz, CV_32F);for (int j = 0; j < sz; j++) {Mat data = (t) ? img.row(j) : img.col(j);mhist.at<float>(j) = countOfBigValue(data, threshold);//统计像素个数}double min, max;minMaxLoc(mhist, &min, &max);//归一化if (max > 0)mhist.convertTo(mhist, -1, 1.0f / max, 0);return mhist;}

5、去处铆钉

处理车牌上铆钉和水平线,因为铆钉和字符连在一起,会影响后面识别的精度。此处有一个特别的乌龙事件,就是铆钉的读音应该是maoding,不读liuding;基本思路是:依次扫描各行,判断跳变的次数,字符所在行跳变次数会很多,但是铆钉所在行则偏少,将每行中跳变次数少于7的行判定为铆钉,清除影响。返回false表示这个Mat不是车牌。

  bool clearLiuDing(Mat &img) {std::vector<float> fJump;int whiteCount = 0;const int x = 7;//跳变次数小于7的是铆钉Mat jump = Mat::zeros(1, img.rows, CV_32F);for (int i = 0; i < img.rows; i++) {int jumpCount = 0;for (int j = 0; j < img.cols - 1; j++) {if (img.at<char>(i, j) != img.at<char>(i, j + 1)) jumpCount++;//统计相邻像素的跳变次数if (img.at<uchar>(i, j) == 255) {whiteCount++;}}jump.at<float>(i) = (float) jumpCount;//jump存放每行像素的跳变次数}int iCount = 0;for (int i = 0; i < img.rows; i++) {fJump.push_back(jump.at<float>(i));if (jump.at<float>(i) >= 16 && jump.at<float>(i) <= 45) {// jump conditioniCount++;}}// if not is not plateif (iCount * 1.0 / img.rows <= 0.40) {//跳变次数满足条件的行数如果少于40%,可能这个区域都不是车牌return false;}if (whiteCount * 1.0 / (img.rows * img.cols) < 0.15 ||whiteCount * 1.0 / (img.rows * img.cols) > 0.50) {//白色像素数量不满足条件的也不是车牌return false;}for (int i = 0; i < img.rows; i++) {if (jump.at<float>(i) <= x) {for (int j = 0; j < img.cols; j++) {img.at<char>(i, j) = 0;}}}return true;}

6、 spatial_ostu

空间otsu算法,主要用于处理光照不均匀的图像,对于当前图像,分块分别进行二值化;主要是为了应对左右光照不一致的情况,譬如车牌的左边部分光照比右边部分要强烈的多,通过图像分块处理,提高otsu分割的鲁棒性;

void spatial_ostu(InputArray _src, int grid_x, int grid_y, Color type) {Mat src = _src.getMat();int width = src.cols / grid_x;//分成多少块int height = src.rows / grid_y;// iterate through gridfor (int i = 0; i < grid_y; i++) {for (int j = 0; j < grid_x; j++) {Mat src_cell = Mat(src, Range(i * height, (i + 1) * height), Range(j * width, (j + 1) * width));if (type == BLUE) {cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);} else if (type == YELLOW) {cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);} else if (type == WHITE) {cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);} else {cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);}}}}

推荐一个博客:EasyPR源码剖析(8):字符分割

easyPR源码解析之chars_segment.h相关推荐

  1. easyPR源码解析之ann_train.h/config.h

    ann_train.h源码定义一个 AnnTrain类,该类继承自ITrain类(在train.h文件中): #include "easypr/train/train.h" #in ...

  2. easyPR源码解析之chars_identify.h

    在上一篇文章的介绍中,我们已经通过相应的字符分割方法,将车牌区域进行分割,得到7个分割字符图块,接下来要做的就是将字符图块放入训练好的神经网络模型,通过模型来预测每个图块所表示的具体字符.本节主要介绍 ...

  3. easyPR源码解析之plate_judge.h

    #ifndef EASYPR_CORE_PLATEJUDGE_H_ #define EASYPR_CORE_PLATEJUDGE_H_ #include "easypr/core/plate ...

  4. easyPR源码解析之plate_locate.h

    从今天开始,准备一点一点的啃代码easyPR项目. 我们先从libesypr文件下的源文件/core/plate_locate.cpp开始: plate_locate.cpp文件中包含的头文件如下: ...

  5. 【darknet源码解析-24】shortcut_layer.h 和 shortcut_layer.c 解析

    本系列为darknet源码解析,本次解析src/short_layer.h 与 src/short_layer.c 两个.在yolo v3中short_layer主要完成直连操作,完成残差块中的恒等映 ...

  6. H.264压缩技术之视频基础(foundation of learning video)——Matlab源码解析

    前言 为了后续能更好的理解,I帧编码与P帧编码,所以笔者先对数字视频中的一些基础概念进行铺垫.后续比较复杂的帧内预测,与帧间预测理解起来就会相对容易些. 关于Matlab中h.264的main函数部分 ...

  7. 谷歌BERT预训练源码解析(二):模型构建

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

  8. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

  9. libev源码解析——监视器(watcher)结构和组织形式

    在<libev源码解析--总览>中,我们介绍了libev的一些重要变量在不同编译参数下的定义位置.由于这些变量在多线程下没有同步问题,所以我们将问题简化,所提到的变量都是线程内部独有的,不 ...

最新文章

  1. python【蓝桥杯vip练习题库】ADV-77统计平均成绩
  2. 信息系统项目管理师全国通用吗
  3. Java学习笔记——函数式接口
  4. 吕布机器人唤醒方式能换么_《王者荣耀》推吕布智能机器人,网友:小学生受到1万点暴击伤害...
  5. STM32----TIM6和TIM7
  6. mysql语言的创建模式文件_南开17春学期《数据库应用系统设计》在线作业 免费答案...
  7. 【C++】C++中的头文件(.h)—详解(1)
  8. Java 8 特性 – 终极手册(一)
  9. 华为顶尖应届生最高年薪超 200 万;抖音服务器宕机;GitLab 12.1 发布 | 极客头条...
  10. html文档树形结构图
  11. 区块链应用如何实现资金盘分红
  12. 带你认识Oracle索引类型(全面总结)
  13. 服务器磁盘阵列数据恢复方法和数据恢复过程详解
  14. 两台计算机如何传输数据,详解两台电脑直连传输数据方法
  15. linux 同步北京时间_Linux系统同步系统时间为北京时间
  16. JS--统一社会信用代码校验
  17. 善用 Google 的 手气不错 I'm feeling lucky 搜索
  18. 基于STM32单片机智能手环脉搏心率检测计步器原理图PCB
  19. 胡耀文教你:裂变8级、转化率32%、K值7.4的老带新式分销全复盘
  20. 医院影像图像科室工作站PACS系统 DICOM 三维图像后处理与重建

热门文章

  1. 服务器项目有哪些,怎么部署项目到服务器?服务器的特性有哪些?
  2. 计算机适配器有什么作用,例举适配器是什么
  3. java 信号量 互斥锁_线程同步(互斥锁与信号量的作用与区别)
  4. 蓝牙耳机声音一顿一顿的_这次让世界听听我们的声音——声阔SoundcoreLiberty2Pro蓝牙耳机...
  5. 携带token的ajax请求方法封装
  6. 算法与数据结构(六):堆排序
  7. mybatis分页数据重复
  8. 计算机网络(9)-----TCP可靠传输的实现
  9. 自学PHP有哪些书籍和教程值得推荐?
  10. 关于div中图片水平垂直居中的问题