├── 0000.jpg ├── 0308.jpg ├── images ├── 0000.jpg └── 0308.jpg ├── pdf ├── 单排字符分割1.pdf ├── 单排字符分割2.pdf └── 双排字符车牌分隔.pdf ├── arrows ├── arrow1.png └── arrow2.png ├── results ├── 0000.jpg └── 0308.jpg ├── run.sh ├── CMakeLists.txt ├── segment.h ├── README.md ├── segment.cpp ├── seg_double.cpp ├── seg_single.cpp └── methods.cpp /0000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/0000.jpg -------------------------------------------------------------------------------- /0308.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/0308.jpg -------------------------------------------------------------------------------- /images/0000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/images/0000.jpg -------------------------------------------------------------------------------- /images/0308.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/images/0308.jpg -------------------------------------------------------------------------------- /pdf/单排字符分割1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/pdf/单排字符分割1.pdf -------------------------------------------------------------------------------- /pdf/单排字符分割2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/pdf/单排字符分割2.pdf -------------------------------------------------------------------------------- /arrows/arrow1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/arrows/arrow1.png -------------------------------------------------------------------------------- /arrows/arrow2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/arrows/arrow2.png -------------------------------------------------------------------------------- /pdf/双排字符车牌分隔.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/pdf/双排字符车牌分隔.pdf -------------------------------------------------------------------------------- /results/0000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/results/0000.jpg -------------------------------------------------------------------------------- /results/0308.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chaway/LP_character_segmentation/HEAD/results/0308.jpg -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | make; 3 | dir="./images/" 4 | echo $dir 5 | filenames=`ls $dir` 6 | for input_filename in $filenames 7 | do 8 | echo ${dir}${input_filename} 9 | # filename=${input_filename%.*} 10 | # extension=${input_filename##*.} 11 | # ./segment $filename $extension 12 | ./segment ${dir}${input_filename} 13 | printf "\n" 14 | done 15 | 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # cmake needs this line 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | # Define project name 5 | project(LP_character_segmentation) 6 | 7 | # Find OpenCV, you may need to set OpenCV_DIR variable 8 | # to the absolute path to the directory containing OpenCVConfig.cmake file 9 | # via the command line or GUI 10 | find_package(OpenCV REQUIRED) 11 | 12 | # If the package has been found, several variables will 13 | # be set, you can find the full list with descriptions 14 | # in the OpenCVConfig.cmake file. 15 | # Print some message showing some of them 16 | message(STATUS "OpenCV library status:") 17 | message(STATUS " version: ${OpenCV_VERSION}") 18 | message(STATUS " libraries: ${OpenCV_LIBS}") 19 | message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}") 20 | 21 | if(CMAKE_VERSION VERSION_LESS "2.8.11") 22 | # Add OpenCV headers location to your include paths 23 | include_directories(${OpenCV_INCLUDE_DIRS}) 24 | endif() 25 | 26 | # Declare the executable target built from your sources 27 | add_executable(segment segment.cpp seg_single.cpp seg_double.cpp methods.cpp segment.h) 28 | 29 | # Link your application with OpenCV libraries 30 | target_link_libraries(segment ${OpenCV_LIBS}) 31 | -------------------------------------------------------------------------------- /segment.h: -------------------------------------------------------------------------------- 1 | #include "opencv2/core.hpp" 2 | #include "opencv2/imgproc.hpp" 3 | #include "opencv2/highgui.hpp" 4 | #include "opencv2/videoio.hpp" 5 | #include 6 | #include 7 | 8 | #define SINGLE 1 //单排字符标志 9 | #define DOUBLE 2 //双排字符车牌 10 | #define UNCERTAIN 3 //无法从比例确定 11 | #define MAX_TIMES 3 //查找字符最大次数 12 | #define ELE_WIDTH 10 //腐蚀模板的宽度 13 | #define ELE_HEIGHT 10 //腐蚀模板的高度 14 | #define CH 1 //中文字符标志 15 | #define LET_NUM 2 //数字字符或英文字符标志 16 | #define BLUEWHITE 0 17 | #define BLACKYELLOW 1 18 | 19 | 20 | 21 | 22 | int judge_type(cv::Mat); //判断输入车牌类型 23 | int judge_color(cv::Mat); //判断输入车牌颜色类型 24 | int segment_single(cv::Mat&); //单排字符车牌分割函数 25 | //void find_ch_si(std::vector & proposal_ch,int & num_ch,float (&b)[2],float (&k)[2],int & result_index); //merge boxes of proposal 26 | int segment_double(cv::Mat&); //双排字符车牌分割函数 27 | 28 | 29 | //common methods 30 | void revise_re(cv::Rect& r,int mor_times,cv::Mat); //修正矩形框的边缘(经过腐蚀之后检测出的轮廓与实际轮廓有误差) 31 | //void remove_rivet(cv::Mat & image_thresh,int & upline,int & downline); 32 | 33 | //void draw_rect_or(cv::Mat & image_or,cv::Rect &r); //在原车牌图像中画出检测出字符的外接矩形框 34 | void draw_rect(cv::Rect & r,cv::Mat& input_img,cv::Mat& image_co,int numclass); //在二值图像上画出字符的外接矩形框 35 | void merge(cv::Rect & subr1,cv::Rect & subr2); //合并两个矩形框 36 | void rect_zero(cv::Mat& img,cv::Rect r);//矩形区域清零 37 | 38 | void linear_regression(std::vector & let_num,float (&a)[2],float (&b)[2]); //最小二乘拟合直线 39 | float numzero(std::vector & contour,cv::Rect & r); //计算矩形区域中白色像素的比例 40 | 41 | void setzero(cv::Mat & image_co,float (&b)[2],float (&k)[2]); //选定区域像素灰度值清零 42 | 43 | 44 | void gamma(cv::Mat & image_gray); //图像gamma变换 45 | //void drawHist(cv::Mat &src,std::string& s_temp);//画出灰度直方图 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 车牌字符分割(License Plate Character Segmentation) 2 | ------- 3 | 该 **project** 实现了单排或双排字符车牌分割的功能。 **project** 基于 **OpenCV 3.1.0**,在 **Ubuntu 16.04** 环境中完成编写与测试,所以在使用程序时需要先配置好 **OpenCV 3.1.0**,配置方法可以参考下面列出的链接。此外,程序在 **Linux** 下详细的使用方法也可以参考下面的教程。 4 | 5 | 6 | Example 7 | --- 8 | * 单排字符车牌分割 9 | 10 | 0000.jpg arrow1.png 0000.jpg 11 | 12 | * 双排字符车牌分割 13 | 14 | 0308.jpg arrow2.png0308.jpg 15 | 16 | 17 | Ubuntu 16.04 下 OpenCV 环境配置 18 | ------ 19 | **OpenCV** 配置的教程网上有许多,这里不再详细介绍,可以参考以下链接 20 | - [Tutorial linux install](http://docs.opencv.org/3.1.0/d7/d9f/tutorial_linux_install.html) 21 | - [Install opencv 3.0 and python 2.7 on ubuntu](http://www.pyimagesearch.com/2015/06/22/install-opencv-3-0-and-python-2-7-on-ubuntu/) 22 | - [OpenCV on GitHub](https://github.com/opencv/opencv) 23 | 24 | 25 | 26 | 下载 27 | ------ 28 | 在终端中执行下列命令复制 ***repository*** 到本地,或者可以直接下载 ***.zip*** 格式的压缩文件 29 | ``` 30 | git clone https://github.com/Chaway/LP_character_segmentation.git 31 | ``` 32 | 33 | 34 | 程序编译 35 | ------- 36 | 配置完 ***OpenCV*** 环境后,需要对程序进行编译。可以借助 ***CMake*** 工具自动生成 ***Makefile***,再使用 ***make*** 命令进行编译链接。在终端中进入程序所在目录 37 | ``` 38 | cd LP_character_segmentation/ 39 | ``` 40 | 然后在终端输入下列命令即可编译完成,**编译成功后会在目录下生成文件名为 *segment* 的二进制可执行文件** 41 | ``` 42 | cmake ./;make 43 | ``` 44 | 若要添加或删减程序文件,编译前需要相应地修改目录下的 ***CMakeLists.txt*** 45 | 46 | 47 | 运行 48 | ----- 49 | 50 | - **单次分割** 51 | 52 | 在终端执行程序,***filename*** 是图像文件的文件名(程序文件和待分割图像都在当前目录下,否则需要指明路径) 53 | 54 | ``` 55 | ./segment filename 56 | ``` 57 | 58 | - **批量分割** 59 | 60 | ***bash*** 脚本 ***run.sh*** 可以对 ***image*** 目录下的所有车牌图像进行分割,**批量分割时可以将 *segment.cpp* 中显示分割结果的部分代码注释掉** 61 | 62 | ``` 63 | ./run.sh 64 | ``` 65 | 分割结果保存在当前目录下的 ***results*** 目录中。 66 | 67 | 原理 68 | ------ 69 | 程序用到的车牌字符分割原理可以参考 ***pdf*** 目录下的文档 70 | 71 | -------------------------------------------------------------------------------- /segment.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | project description: 3 | License plate character segmentation based on OpenCV 3.1.0 4 | 基于OpenCV 3.1.0的车牌字符分割,使用方法详见README.md 5 | version 0.1.0 6 | 7 | */ 8 | 9 | #include "segment.h" 10 | using namespace std; 11 | using namespace cv; 12 | 13 | int main(int argc,char ** argv) 14 | { 15 | Mat input_img; 16 | input_img = imread(argv[1]); //读取input image 17 | if(!input_img.data) 18 | { 19 | cout << "Open input image failed,please check input image!" << endl; 20 | exit(0); 21 | } 22 | 23 | cout << "width = " << input_img.cols << " , height = " << input_img.rows << endl; 24 | 25 | int type = judge_type(input_img); //判断input image的type 26 | int num_si = 0 , num_do = 0; 27 | /* 28 | 判断输入车牌图像是单排字符图像还是双排字符图像,然后根据判断结果进行分割 29 | */ 30 | switch(type) 31 | { 32 | case SINGLE: 33 | cout << "Single row character license plate" << endl; 34 | //cout << input_img.cols/(float)input_img.rows << endl; 35 | num_si = segment_single(input_img); //进行单排字符车牌分割 36 | if(num_si != 7) 37 | { 38 | cout << "Segmentation fail" << endl; 39 | return -1; 40 | } 41 | cout << "Segmentation success" << endl; 42 | break; 43 | 44 | case DOUBLE: 45 | cout << "Double row character license plate" << endl; 46 | num_do = segment_double(input_img); //进行双排字符车牌分割 47 | if(num_do != 7) 48 | { 49 | cout << "Segmentation fail" << endl; 50 | return -1; 51 | } 52 | cout << "Segmentation success" << endl; 53 | break; 54 | 55 | case UNCERTAIN: 56 | cout << "Uncertain by ratio" << endl; 57 | num_si = segment_single(input_img); 58 | if(num_si == 7) 59 | { 60 | cout << "Single row character license plate" << endl; 61 | cout << "Segmentation success" << endl; 62 | } 63 | else 64 | { 65 | input_img = imread(argv[1]); 66 | num_do = segment_double(input_img); 67 | if(num_do == 7) 68 | { 69 | cout << "Double row character license plate" << endl; 70 | cout << "Segmentation success" << endl; 71 | } 72 | else 73 | { 74 | cout << "Segmentation fail" << endl; 75 | return -1; 76 | } 77 | } 78 | break; 79 | 80 | default: 81 | return -1; 82 | } 83 | 84 | /*将分割结果写入磁盘,路径可以自定义*/ 85 | string input_name = argv[1]; 86 | unsigned long pos = input_name.find_last_of("/"); 87 | string filename = input_name.substr(pos+1); //截取文件名 88 | string dir = "./results/"; //输出路径 89 | string output_name = dir + filename; 90 | imwrite(output_name,input_img); 91 | 92 | /*显示分割结果*/ 93 | // imshow("Input image", input_img); 94 | // cout << "Press any key to exit.." << endl; 95 | // waitKey(); 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /seg_double.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 双排字符车牌分割 3 | */ 4 | #include "segment.h" 5 | 6 | #define WIDTH_DO 450 //车牌归一化处理宽度 7 | #define HEIGHT_DO 300 //车牌归一化处理高度 8 | #define THRESHOLD_DO 75 //二值化处理阈值 9 | 10 | using namespace std; 11 | using namespace cv; 12 | 13 | 14 | void preprocess_do(Mat &input_img) 15 | { 16 | cvtColor(input_img,input_img,COLOR_BGR2GRAY); //RGB图像转为灰度图像 17 | resize(input_img,input_img,Size(WIDTH_DO,HEIGHT_DO)); //线性插值大小归一化 18 | equalizeHist(input_img,input_img); //直方图均衡化处理 19 | threshold(input_img,input_img,THRESHOLD_DO,255,CV_THRESH_BINARY_INV); //阈值化处理,与单排车牌黑白相反 20 | //imwrite("./thresh_do.jpg",input_img); 21 | } 22 | 23 | void filter_do(Mat& image_co,Mat& input_img,vector< vector< Point> >& contours,vector& proposal_ch,vector& let_num,int& num_ch,int mor_times,int times) 24 | { 25 | for (int i = 0;i < contours.size();i++) 26 | { 27 | Rect r = boundingRect(Mat(contours[i])); //获取轮廓的bounding box 28 | if(times != 0) 29 | { 30 | revise_re(r,mor_times,input_img); 31 | } 32 | 33 | /* 34 | 根据位置分为三个区域进行筛选(1.第一排的中文字符区域, 35 | 2.第一排的英文字符区域,3.第二排的字符区域),去除干扰区 36 | 域的筛选条件与单排字符分割相似,主要用到了位置信息,大小 37 | 信息,比例信息以及黑白面积比信息。 38 | */ 39 | if(times == 0) 40 | { 41 | if(r.y + r.height/2 < HEIGHT_DO/2 && r.x + r.width/2 < WIDTH_DO/2 - 30) 42 | { 43 | if(r.x + r.width < WIDTH_DO/2 && r.width >= 10 && r.height >= 10)//get Chinese Character 44 | { 45 | if(r.x > 50) 46 | { 47 | //case 1:part of Chinese character 48 | proposal_ch.push_back(r); 49 | rect_zero(image_co,r); 50 | } 51 | } 52 | } 53 | } 54 | 55 | if(r.y + r.height/2 < HEIGHT_DO/2 && r.x + r.width/2 > WIDTH_DO/2 && r.width < WIDTH_DO/2 && r.height < HEIGHT_DO/2) 56 | { 57 | //case 2:Letter in up line 58 | //第一排的英文字符情况比较简单,无需存入let_num 59 | if(r.width > 80 && r.height > 80 && r.height/r.width < 3) 60 | { 61 | draw_rect(r,input_img,image_co,LET_NUM); //画出英文或数字字符的边框 62 | //draw_rect_or(image_or,r); 63 | num_ch++; 64 | } 65 | } 66 | else if(r.y + r.height/2 > HEIGHT_DO/2 && WIDTH_DO/60 < r.width && r.width < WIDTH_DO/4.0 && r.height > HEIGHT_DO/3 && r.height < HEIGHT_DO - 100) 67 | { 68 | if((r.x + r.width) > WIDTH_DO - 30 || r.x < 30) 69 | { 70 | //case 3.1&3.2:left & right side letter or number character 71 | if(r.height/r.width < 8 && r.width > WIDTH_DO/27 && numzero(contours[i],r) > 0.25 ) 72 | { 73 | if(r.height/r.width < 3 || numzero(contours[i],r) < 0.8) 74 | { 75 | let_num.push_back(r); 76 | draw_rect(r,input_img,image_co,LET_NUM); 77 | //draw_rect_or(image_or,r); 78 | num_ch++; 79 | } 80 | } 81 | } 82 | else if(r.height/(float)(r.width) < 8.0) 83 | { 84 | //case 3.3:down center letter or number character 85 | let_num.push_back(r); 86 | draw_rect(r,input_img,image_co,LET_NUM); 87 | //draw_rect_or(image_or,r); 88 | num_ch++; 89 | //cout << num_ch << endl; 90 | } 91 | } 92 | } 93 | } 94 | 95 | void find_ch_do(vector& proposal_ch,int& num_ch,float (&b)[2],float (&k)[2],int& result_index) 96 | { 97 | int size = proposal_ch.size(); 98 | result_index = 0; 99 | int flag = 0; 100 | 101 | for(int i = 0 ; i < size ; i++ ) 102 | { /*候选轮廓与回归直线的位置比较*/ 103 | if((proposal_ch[i].y + proposal_ch[i].height - 1) >= (int)(k[0]*proposal_ch[i].x + b[0])) 104 | if((proposal_ch[i].y + proposal_ch[i].height - 1) >= (int)(k[0]*(proposal_ch[i].x + proposal_ch[i].width - 1) + b[0])) 105 | { 106 | continue; 107 | } 108 | 109 | flag ++ ; 110 | 111 | if(flag == 1) 112 | { 113 | result_index = i; 114 | continue; 115 | } 116 | 117 | merge(proposal_ch[i],proposal_ch[result_index]); //合并后的结果储存在下标result_index的单元中 118 | result_index = i; 119 | } 120 | /*如果没有合并出汉字,则返回proposal_ch的size值*/ 121 | if(flag == 0) 122 | { 123 | //cout << "Can't find Chinese character!" << endl; 124 | result_index = size; 125 | } 126 | num_ch ++; 127 | } 128 | 129 | /* 130 | 双排字符图像分割,与单排分割的主要区别在于filter_do筛选方法,总的分割流程相同 131 | */ 132 | int segment_double(Mat& input_img) 133 | { 134 | preprocess_do(input_img); //图像预处理 135 | 136 | Mat image_co = input_img.clone(); 137 | cvtColor(input_img,input_img,COLOR_GRAY2BGR);//将预处理后的图像重新转换为RGB三通道(方便在图像上画出彩色的矩形框) 138 | 139 | vector< vector< Point> > contours;//存放检测到的轮廓 140 | Mat element = getStructuringElement(MORPH_CROSS,Size(ELE_WIDTH,ELE_HEIGHT));//定义腐蚀模板 141 | vector proposal_ch;//存放汉字的候选子区域 142 | vector let_num; //存放第二排的数字和英文字符 143 | 144 | int flag_ch = 0;//进行过中文字符合并的标志 145 | int num_ch = 0;//找到的字符个数 146 | int mor_times = 0; //腐蚀次数 147 | 148 | for(int times = 0 ; times < MAX_TIMES ; times ++) 149 | { 150 | Mat temp_co = image_co.clone(); //下一步调用findContours时会对输入图像进行变换,为了保留image_co的数据 151 | findContours(temp_co,contours,RETR_EXTERNAL,CHAIN_APPROX_NONE); //寻找输入图像的轮廓,轮廓存放在contours中(调用库函数) 152 | 153 | 154 | filter_do(image_co,input_img,contours,proposal_ch,let_num,num_ch,mor_times,times);//筛选字符区域 155 | 156 | if(num_ch >= 3 && flag_ch == 0) 157 | { 158 | flag_ch = 1; 159 | int result_index;//记录中文字符bounding box的位置 160 | float b[2]={0};//字符区域上下边界直线的截距 161 | float k[2]={0};//字符区域上下边界直线的斜率 162 | 163 | linear_regression(let_num,b,k);//最小二乘法拟合出第二排字符区域上下边界 164 | 165 | find_ch_do(proposal_ch,num_ch,b,k,result_index); //得出中文字符的外接矩形框 166 | 167 | if(result_index != proposal_ch.size()) 168 | { 169 | draw_rect(proposal_ch[result_index],input_img,image_co,CH);//在图像上画出中文字符的边框 170 | //draw_rect_or(image_or,proposal_ch[result_index]); 171 | } 172 | else 173 | { 174 | cout << "Can't find Chinese character!" << endl; 175 | return num_ch; 176 | } 177 | } 178 | else 179 | { 180 | Mat image_mor = image_co.clone(); 181 | morphologyEx(image_mor,image_co,MORPH_ERODE,element);//对图像进行形态学腐蚀处理 182 | mor_times ++; 183 | } 184 | 185 | if(num_ch == 7) 186 | { 187 | //cout << "times = " << times << endl; 188 | break; 189 | } 190 | } 191 | return num_ch; 192 | } 193 | -------------------------------------------------------------------------------- /seg_single.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 单排字符车牌分割 3 | */ 4 | 5 | #include "segment.h" 6 | 7 | #define WIDTH_SI 1050 //车牌归一化处理宽度 8 | #define HEIGHT_SI 300 //车牌归一化处理高度 9 | #define THRESHOLD_SI 180 //二值化处理阈值 10 | #define THRESHOLD_SI_INV 75 //二值化处理阈值 11 | using namespace std; 12 | using namespace cv; 13 | 14 | 15 | /* 16 | 图像预处理 17 | */ 18 | void preprocess_si(Mat& input_img) 19 | { 20 | cvtColor(input_img,input_img,COLOR_BGR2GRAY); //RGB图像转为灰度图像 21 | //int color_type = judge_color(input_img); 22 | resize(input_img,input_img,Size(WIDTH_SI,HEIGHT_SI)); //线性插值大小归一化 23 | equalizeHist(input_img,input_img); //直方图均衡化处理 24 | //int color_type = judge_color(input_img); 25 | //threshold(input_img, input_img, color_type == BLUEWHITE ? THRESHOLD_SI :THRESHOLD_SI_INV, 26 | // 255,color_type == BLUEWHITE ? CV_THRESH_BINARY : CV_THRESH_BINARY_INV); 27 | threshold(input_img,input_img,THRESHOLD_SI,255,CV_THRESH_BINARY); 28 | //imwrite("./thresh_si.jpg",input_img); 29 | } 30 | 31 | 32 | /* 33 | 字符区域筛选 34 | */ 35 | void filter_si(Mat& image_co,Mat& input_img,vector< vector< Point> >& contours,vector& proposal_ch,vector& let_num,int& num_ch,int mor_times,int times) 36 | { 37 | for (int i = 0;i < contours.size();i++) 38 | { 39 | Rect r = boundingRect(Mat(contours[i])); //获取轮廓的bounding box(调用库函数) 40 | 41 | if(times != 0) 42 | { 43 | revise_re(r,mor_times,input_img); //如果经过腐蚀则要修正矩形框位置 44 | } 45 | 46 | 47 | /*筛选中文字符的可能子区域*/ 48 | if(times == 0) 49 | { 50 | if(r.x + r.width < WIDTH_SI/5 && r.width >= 10 && r.height >= 10) //大小先验信息 51 | { 52 | if(r.x < 30) 53 | { 54 | if((r.y + r.height) > HEIGHT_SI - 10 || r.y < 10) //位置先验信息 55 | { 56 | //case 1.1 & 1.2:left down & up 57 | if(r.height/r.width < 3 && numzero(contours[i],r) > 0.35) //比例和面积比先验信息 58 | { 59 | proposal_ch.push_back(r); 60 | rect_zero(image_co,r); 61 | } 62 | } 63 | else 64 | { 65 | //case 1.3:left center 66 | if(r.height/r.width < 3) 67 | { 68 | proposal_ch.push_back(r); 69 | rect_zero(image_co,r); 70 | } 71 | } 72 | } 73 | else 74 | { 75 | //case 1.4:proposal region 76 | proposal_ch.push_back(r); 77 | rect_zero(image_co,r); 78 | continue; //防止与右边字符重复分割 79 | } 80 | } 81 | } 82 | /*筛选英文和数字字符区域*/ 83 | if(r.x > 100 && WIDTH_SI/60 < r.width && r.width < WIDTH_SI/5 && r.height > HEIGHT_SI/3 && r.height < HEIGHT_SI - 5) 84 | { 85 | if((r.x + r.width) > WIDTH_SI - 30) 86 | { 87 | //case 2.1:right 88 | if(r.height/r.width < 8 && r.width > HEIGHT_SI/27 && numzero(contours[i],r) > 0.25) 89 | { 90 | if(r.height/r.width < 3 || numzero(contours[i],r) < 0.8) 91 | { 92 | let_num.push_back(r); 93 | draw_rect(r,input_img,image_co,LET_NUM); 94 | //draw_rect_or(input_img,r); 95 | num_ch++; 96 | } 97 | } 98 | } 99 | else if(r.height/r.width < 8) 100 | { 101 | //case 2.2:normal 102 | let_num.push_back(r); 103 | draw_rect(r,input_img,image_co,LET_NUM); 104 | //draw_rect_or(input_img,r); 105 | num_ch++; 106 | //cout << num_ch << endl; 107 | } 108 | } 109 | } 110 | } 111 | 112 | /* 113 | 合并中文字符子区域,得到完整中文字符的外接矩形框 114 | */ 115 | void find_ch_si(vector& proposal_ch,int& num_ch,float (&b)[2],float (&k)[2],int& result_index) 116 | { 117 | int size = proposal_ch.size(); 118 | result_index = 0; 119 | 120 | int flag = 0; 121 | 122 | for(int i = 0 ; i < size ; i++ ) 123 | { 124 | /*候选轮廓与两条字符区域边界的回归直线的位置比较*/ 125 | if((proposal_ch[i].y + proposal_ch[i].height - 1) <= (int)(k[0]*proposal_ch[i].x + b[0])) 126 | if((proposal_ch[i].y + proposal_ch[i].height - 1) <= (int)(k[0]*(proposal_ch[i].x + proposal_ch[i].width - 1) + b[0])) 127 | { 128 | continue; 129 | } 130 | 131 | if((proposal_ch[i].y - 1) >= (int)(k[1]*proposal_ch[i].x + b[1])) 132 | if((proposal_ch[i].y - 1) >= (int)(k[1]*(proposal_ch[i].x + proposal_ch[i].width - 1) + b[1])) 133 | { 134 | continue; 135 | } 136 | 137 | flag ++ ; 138 | 139 | if(flag == 1) 140 | { 141 | result_index = i; 142 | continue; 143 | } 144 | 145 | merge(proposal_ch[i],proposal_ch[result_index]); //合并后的结果储存在下标result_index的单元中 146 | result_index = i; 147 | } 148 | 149 | /*如果没有合并出汉字,则返回proposal_ch的size值*/ 150 | if(flag == 0) 151 | { 152 | //cout << "can't find Chinese character!" << endl; 153 | result_index = size; 154 | } 155 | else 156 | num_ch ++; 157 | } 158 | 159 | 160 | /* 161 | 单排字符图像分割 162 | */ 163 | int segment_single(Mat& input_img) 164 | { 165 | preprocess_si(input_img); //图像预处理 166 | Mat image_co = input_img.clone(); //复制图像数据 167 | cvtColor(input_img,input_img,COLOR_GRAY2BGR); //将预处理后的图像重新转换为RGB三通道(方便在图像上画出彩色的矩形框) 168 | 169 | vector< vector< Point> > contours; //存放检测到的轮廓 170 | Mat element = getStructuringElement(MORPH_CROSS,Size(ELE_WIDTH,ELE_HEIGHT)); //定义腐蚀模板 171 | vector proposal_ch; //存放汉字的候选子区域 172 | vector let_num; //存放数字和英文字 173 | 174 | int flag_ch = 0; //进行过中文字符合并的标志 175 | int mor_times = 0; //腐蚀次数 176 | int num_ch = 0; //找到的字符个数 177 | 178 | for(int times = 0;times < MAX_TIMES;times ++) 179 | { 180 | Mat temp_co = image_co.clone(); //下一步调用findContours时会对输入图像进行变换,为了保留image_co的数据 181 | findContours(temp_co,contours,RETR_EXTERNAL,CHAIN_APPROX_NONE); //寻找输入图像的轮廓,轮廓存放在contours中(调用库函数) 182 | 183 | filter_si(image_co,input_img,contours,proposal_ch,let_num,num_ch,mor_times,times); //筛选字符区域 184 | 185 | if(num_ch >= 4 && flag_ch == 0) 186 | { 187 | flag_ch = 1; 188 | int result_index;//记录中文字符bounding box的位置 189 | float b[2]={0}; //字符区域上下边界直线的截距 190 | float k[2]={0}; //字符区域上下边界直线的斜率 191 | 192 | linear_regression(let_num,b,k); //最小二乘法拟合出字符区域上下边界 193 | //cout << "(b,k) = " << b[0] <<" " << k[0] << " " << b[1] <<" " << k[1] < 0) ? (r.x - mtime*ELE_WIDTH/2) : 1; 55 | r.y = ((r.y - mtime*ELE_HEIGHT) > 0) ? (r.y - mtime*ELE_HEIGHT/2) : 1; 56 | r.width = ((r.x + r.width + mtime*ELE_WIDTH - 1) <= image.cols) ? (r.width + mtime*ELE_WIDTH) : (image.cols - r.x + 1); 57 | r.height = ((r.y + r.height + mtime*ELE_HEIGHT - 1) <= image.rows) ? (r.height + mtime*ELE_HEIGHT) : (image.rows - r.y + 1); 58 | } 59 | 60 | /*画出图像的灰度值直方图,以图像形式表示*/ 61 | void drawHist(Mat& src,string& s_temp) 62 | { 63 | MatND hist; 64 | int dims = 1; 65 | int bins = 256; 66 | int hist_size[] = {bins}; 67 | float range[] = { 0, 256 }; 68 | const float* ranges[] = {range}; 69 | 70 | int channels[] = {0}; 71 | 72 | calcHist(&src, 1, channels, Mat(),hist,dims,hist_size,ranges); //调用库函数计算灰度直方图 73 | 74 | 75 | int hist_height=256; 76 | 77 | double max_val; 78 | double min_val; 79 | int scale = 2; 80 | minMaxLoc(hist, &min_val, &max_val, 0, 0); 81 | 82 | Mat hist_img(hist_height,bins*scale,CV_8U,Scalar(0)); 83 | 84 | //cout<<"max_val = "<(i); 89 | int intensity = cvRound(bin_val*hist_height/max_val); 90 | 91 | rectangle(hist_img,Point(i*scale,hist_height-1), 92 | Point((i+1)*scale - 1, hist_height - intensity), 93 | Scalar(255)); 94 | } 95 | 96 | } 97 | 98 | 99 | /* 100 | void remove_rivet(Mat& image_thresh,int& upline,int& downline) 101 | { 102 | //cout << image_thresh.rows << " >>>>>>>>>>>" <(i,j) - (int)image_thresh.at(i,j-1)); 113 | // if(i ==1) 114 | // cout << temp << endl; 115 | if( temp == 255) 116 | { 117 | changetimes[i] ++; 118 | } 119 | } 120 | } 121 | 122 | 123 | for(int start = image_thresh.rows/2;start >=1;start --) 124 | { 125 | if (changetimes[start] < changetimes_thresh) 126 | { 127 | upline = start; 128 | cout << "upline = " << upline << endl; 129 | 130 | break; 131 | } 132 | } 133 | 134 | for(int start = image_thresh.rows/2;start < image_thresh.rows;start ++) 135 | { 136 | if (changetimes[start] < changetimes_thresh) 137 | { 138 | downline = start; 139 | cout << "downline = " << downline << endl; 140 | break; 141 | } 142 | } 143 | 144 | // for(int row = 0;row <= upline;row ++) 145 | // { 146 | // image_thresh.rows(row).setTo(Scalar(0)); 147 | // } 148 | image_thresh(Range(0,upline),Range::all()).setTo(Scalar(0)); 149 | 150 | // for(int row = downline;row <= image_thresh.rows;row ++) 151 | // { 152 | // Range(0,100),Range(50,200).setTo(Scalar(0)); 153 | // } 154 | image_thresh(Range(downline,image_thresh.rows),Range::all()).setTo(Scalar(0)); 155 | 156 | } 157 | */ 158 | 159 | /*在原始输入图像上画出矩形框*/ 160 | void draw_rect_or(Mat& image_or,Rect& r,int COLS,int ROWS) 161 | { 162 | Rect re_temp; 163 | re_temp.x = (int)((float)(r.x*image_or.cols)/COLS); 164 | re_temp.y = (int)((float)(r.y*image_or.rows)/ROWS); 165 | re_temp.width = (int)((float)(r.width*image_or.cols)/COLS); 166 | re_temp.height = (int)((float)(r.height*image_or.rows)/ROWS); 167 | rectangle(image_or,re_temp,Scalar(255,0,255),1); 168 | } 169 | 170 | /*不同类型的字符画出不同颜色的矩形框*/ 171 | void draw_rect(Rect& r,Mat& input_img,Mat& image_co,int character_class) 172 | { 173 | switch(character_class){ 174 | case CH: 175 | rectangle(input_img, r, Scalar(0,255,255),3); 176 | break; 177 | case LET_NUM: 178 | rectangle(input_img, r, Scalar(255,0,255),3); 179 | break; 180 | default: 181 | cout << "draw bounding box error" << endl; 182 | }; 183 | rect_zero(image_co,r); 184 | //imwrite("/workspace/LP_character_segmentation/test.jpg",image_co); 185 | //imwrite("/workspace/LP_character_segmentation/input_img.jpg",input_img); 186 | } 187 | 188 | /*对矩形框内的区域清零*/ 189 | void rect_zero(Mat& img,Rect r) 190 | { 191 | img(Range(r.y - 1,r.y + r.height - 1),Range(r.x - 1,r.x + r.width - 1)).setTo(Scalar(0)); 192 | } 193 | 194 | 195 | /*合并两个矩形框*/ 196 | void merge(Rect & subr1,Rect & subr2)//merge two rectangle 197 | { 198 | int x1 = (subr1.x < subr2.x) ? subr1.x :subr2.x; 199 | int y1 = (subr1.y < subr2.y) ? subr1.y :subr2.y; 200 | int x2 = (subr1.x + subr1.width > subr2.x + subr2.width) ? subr1.x + subr1.width :subr2.x + subr2.width; 201 | int y2 = (subr1.y + subr1.height > subr2.y + subr2.height) ? subr1.y + subr1.height :subr2.y + subr2.height; 202 | subr1.x = x1; 203 | subr1.y = y1; 204 | subr1.width = x2 - x1; 205 | subr1.height = y2 - y1; 206 | } 207 | 208 | 209 | /*最小二乘拟合直线*/ 210 | void linear_regression(vector & let_num,float (&a)[2],float (&b)[2]) 211 | { 212 | int size = let_num.size(); 213 | int sum_xy[2]= {0}; 214 | int sum_x_sq[2] = {0}; 215 | int sum_x[2] = {0}; 216 | int sum_y[2] = {0}; 217 | float x_aver[2]; 218 | float y_aver[2]; 219 | for(int i = 0;i < size;i++) 220 | { 221 | sum_xy[0] += (let_num[i].x )*(let_num[i].y); 222 | sum_xy[1] += (let_num[i].x + let_num[i].width - 1)*(let_num[i].y + let_num[i].height - 1); 223 | sum_x_sq[0] += (let_num[i].x)*(let_num[i].x); 224 | sum_x_sq[1] += (let_num[i].x + let_num[i].width - 1)*(let_num[i].x + let_num[i].width - 1); 225 | sum_x[0] += let_num[i].x; 226 | sum_x[1] += let_num[i].x + let_num[i].width - 1; 227 | sum_y[0] += let_num[i].y; 228 | sum_y[1] += let_num[i].y + let_num[i].height - 1; 229 | } 230 | 231 | x_aver[0] = sum_x[0]/(float)size; 232 | x_aver[1] = sum_x[1]/(float)size; 233 | y_aver[0] = sum_y[0]/(float)size; 234 | y_aver[1] = sum_y[1]/(float)size; 235 | 236 | b[0] = (sum_xy[0] - size*x_aver[0]*y_aver[0])/(sum_x_sq[0] - size*x_aver[0]*x_aver[0]); 237 | a[0] = y_aver[0] - b[0]*x_aver[0]; 238 | 239 | b[1] = (sum_xy[1] - size*x_aver[1]*y_aver[1])/(sum_x_sq[1] - size*x_aver[1]*x_aver[1]); 240 | a[1] = y_aver[1] - b[1]*x_aver[1]; 241 | 242 | 243 | } 244 | 245 | 246 | /*计算黑白面积比*/ 247 | float numzero(vector< Point> & contour,Rect & r) //area of while 248 | { 249 | //rectangle(imagedraw_rect_or_thresh, r, Scalar(0,255,255),2); 250 | // int num = 0; 251 | // int numwhite = 0; 252 | // for(int row = r.y + 1 ;row < r.y + r.height - 2;row ++) 253 | // { 254 | // for (int col = r.x + 1 ;col < r.x + r.width - 2;col ++ ) 255 | // { 256 | // //cout << "row = " << row <<" col = " << col << endl; 257 | // //cout << image_thresh.at(row,col)/255 258 | // if(image_thresh.at(row,col)[0] == 0) 259 | // num ++; 260 | // } 261 | // } 262 | // cout << num << endl; 263 | // cout << numwhite << endl; 264 | int num = contourArea(contour); //调用库函数计算轮廓包围的额面积 265 | //cout << num/(float)(r.width*r.height) << endl; 266 | return num/(float)(r.width*r.height); 267 | } 268 | 269 | 270 | 271 | 272 | 273 | /*边界两侧清零*/ 274 | void setzero(Mat& image_co,float (&b)[2],float (&k)[2]) 275 | { 276 | for (int cols = 0; cols < image_co.cols; cols++) { 277 | for (int rows = 0; rows < (int) (cols * k[0] + b[0]); rows++) { 278 | if(rows == image_co.rows) 279 | { 280 | cout << "Eorror in setzero" << endl; 281 | return; 282 | } 283 | image_co.at(rows, cols) = 0; 284 | } 285 | 286 | for (int rows = (int) (cols * k[1] + b[1]); rows < image_co.rows; rows++) { 287 | image_co.at(rows, cols) = 0; 288 | } 289 | } 290 | 291 | } 292 | 293 | /*gamma变换*/ 294 | void gamma(Mat & image_gray) 295 | { 296 | int cor[256]; 297 | float gamma = 1.5; 298 | float max = pow(255,gamma); 299 | for(int i = 0;i < 256;i++) 300 | { 301 | cor[i] = (int)(i*pow(i,gamma)/max); 302 | } 303 | for(int row = 0 ;row < image_gray.rows ;row++ ) 304 | for(int col = 0;col < image_gray.cols; col++) 305 | { 306 | int intensity = image_gray.at(row,col); 307 | image_gray.at(row,col) = cor[intensity]; 308 | } 309 | } 310 | --------------------------------------------------------------------------------