视频效果:https://www.bilibili.com/video/BV1fK4y137ky
上图:
项目介绍:Android+Opencv+Tesseract-ocr识别不同底色的车牌,蓝色,绿色(新能源)车牌
项目步骤:
1、提取屏幕区域
2、提取车牌区域
3、二值化车牌图片
4、Tesseract-ocr识别字符
直接上代码:
想看的再跟我说,后续再详细些写
public class PlateDetector {
private static String TAG="PlateDetector";
//浅蓝0、//黄色1、//品红2、//浅红色3、//蓝色4、//青色5、// 深红色6、//黑色7 车牌蓝底9 车牌绿底10
public static double[][] HSV_VALUE_LOW = {
{10,163,147},//浅蓝0
{77, 163, 147},//黄色1
{146, 212, 140},//品红2
{126,155, 160},//浅红色3
{0, 204, 178},//蓝色4
{35, 163, 147},//青色5
{110,155,160},// 深红色6
{0,0,0},//黑色7
{0,0,192},//标准蓝8
{0,190,190},//车牌蓝底9 暗的TFT:0,190,190 亮的:0,180,190
{22,195,158}//车牌绿底10 暗的TFT H:21 S要调高一点:210 V:211 亮的TFT S值要调底一点:110 10,100,148
};
public static double[][] HSV_VALUE_HIGH = {
{47,255,255},//浅蓝0
{111, 255,255},//黄色1
{241, 255, 255.0},//品红2
{150,255, 255},//浅红色3
{21, 255, 255},//蓝色4
{75, 255.0, 255},//青色5
{150,255,255},// 深红色6
{180,255,120},//黑色7
{45,238,255},//标准蓝8
{28,255,255},//车牌蓝底9 亮暗一样
{73,255,255}//车牌绿底10 暗H:66 亮H:83
};
public String plateDetector(Bitmap bitmap){
String plateStr=null;
Mat mRgba=Bitmap2Mat(bitmap);
/**
* ***********************车牌识别**************
*/
//实现步骤1、直接HSV颜色空间裁剪出来车牌,计算长宽比来过滤掉
//实现步骤2、阈值分割,边缘检测,检测完之后绘制填充
//实现步骤3、填充之后二值化,二值化之后保存下来训练
// show_bitmap(mRgba);//显示图片到View
Mat gray=new Mat();
Imgproc.cvtColor(mRgba,gray,Imgproc.COLOR_BGR2GRAY);//灰度化
Mat binary=new Mat();
Imgproc.Canny(gray,binary,50,150);//二值化 边缘检测
Mat kernel=Imgproc.getStructuringElement(Imgproc.MORPH_RECT,new Size(3,3));// 指定腐蚀膨胀核
Imgproc.dilate(binary,binary,kernel);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy=new Mat();
Imgproc.findContours(binary, contours, hierarchy,
Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);//查找轮廓
double maxArea = 0;
Iterator<MatOfPoint> each = contours.iterator();
while (each.hasNext()) {
MatOfPoint wrapper = each.next();
double area = Imgproc.contourArea(wrapper);
if (area > maxArea) {
maxArea = area;
}
}
Mat result=null;
each = contours.iterator();
while (each.hasNext()) {
MatOfPoint contour = each.next();
double area = Imgproc.contourArea(contour);
if (area > 0.01 * maxArea) {
// 多边形逼近 会使原图放大4倍
Core.multiply(contour, new Scalar(4, 4), contour);
MatOfPoint2f newcoutour = new MatOfPoint2f(contour.toArray());
MatOfPoint2f resultcoutour = new MatOfPoint2f();
double length = Imgproc.arcLength(newcoutour, true);
Double epsilon = 0.01 * length;
Imgproc.approxPolyDP(newcoutour, resultcoutour, epsilon, true);
contour = new MatOfPoint(resultcoutour.toArray());
// 进行修正,缩小4倍改变联通区域大小
MatOfPoint new_contour=new MatOfPoint();
new_contour=ChangeSize(contour);
double new_area = Imgproc.contourArea(new_contour);//轮廓的面积
// 求取中心点
Moments mm = Imgproc.moments(contour);
int center_x = (int) (mm.get_m10() / (mm.get_m00()));
int center_y = (int) (mm.get_m01() / (mm.get_m00()));
Point center = new Point(center_x, center_y);
//最小外接矩形
Rect rect = Imgproc.boundingRect(new_contour);
double rectarea = rect.area();//最小外接矩形面积
if (Math.abs((new_area/rectarea)-1)<0.2){
double wh = rect.size().width / rect.size().height;//宽高比值
if (Math.abs(wh - 1.7) < 0.7 && rect.width > 250) {
// 绘图///
Mat imgSource=mRgba.clone();
// 绘制外接矩形
Imgproc.rectangle(imgSource, rect.tl(), rect.br(),
new Scalar(255, 0, 0), 2);
//*****图片裁剪***可以封装成函数*****************
rect.x+=5;
rect.width-=10;
rect.y+=45;
rect.height-=65;
result=new Mat(imgSource,rect);
//*****图片裁剪***可以封装成函数*****************
Imgproc.pyrUp(result,result);//向上采样,放大图片
}
}
}
}
if (result!=null){
Log.e("PlateDetector", "TFT屏幕裁剪成功: ");
//******使用HSV阈值分割***************************
Mat hsv_img=result.clone();
// save_pic(result,true);
Imgproc.cvtColor(hsv_img,hsv_img,Imgproc.COLOR_BGR2HSV);//Hsv颜色空间转换
//车牌蓝色底9阈值分割
Mat plate_blue = new Mat();
Core.inRange(hsv_img,new Scalar(HSV_VALUE_LOW[9]),new Scalar(HSV_VALUE_HIGH[9]),plate_blue);
int blue_pixle_num=0;
for (int x = 0; x < plate_blue.width(); x++) {
for (int y = 0; y < plate_blue.height(); y++) {
double pixle[] = plate_blue.get(y, x);
if (pixle[0] == 255.0) {// 如果是白色
blue_pixle_num++;
}
}
}
// Log.e("PlateDetector", "蓝色车牌像素面积: "+blue_pixle_num );//42873
if (blue_pixle_num>40000&&blue_pixle_num<70000){
// Log.e("PlateDetector", "进入蓝色车牌识别");
plateStr=rect(plate_blue,result,1);
}
//车牌绿色底10阈值分割
Mat plate_green = new Mat();
Core.inRange(hsv_img,new Scalar(HSV_VALUE_LOW[10]),new Scalar(HSV_VALUE_HIGH[10]),plate_green);
int green_pixle_num=0;
for (int x = 0; x < plate_green.width(); x++) {
for (int y = 0; y < plate_green.height(); y++) {
double pixle[] = plate_green.get(y, x);
if (pixle[0] == 255.0) {// 如果是白色
green_pixle_num++;
}
}
}
// Log.e("PlateDetector", "绿色车牌像素面积: "+green_pixle_num );//42873
if (green_pixle_num>50000&&green_pixle_num<90000){
// Log.e("PlateDetector", "进入绿色车牌识别");
plateStr= rect(plate_green,result,2);
}
}
return plateStr;
/**
* ***********************车牌识别**************
*/
}
//通过HSV阈值得到的new_mask车牌的外矩形,new_src为了切割传进来的值
//需要调整分割出来的矩形宽的长度和宽度 adaptiveThreshold要调节自适应阈值
//temp==1识别蓝色车牌 temp==2识别绿色车牌
public String rect(Mat new_mask,Mat new_src,int temp){
String result_str=null;
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy=new Mat();
Imgproc.findContours(new_mask, contours, hierarchy,
Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);//查找轮廓
double maxArea = 0;
Iterator<MatOfPoint> each = contours.iterator();
while (each.hasNext()) {
MatOfPoint wrapper = each.next();
double area = Imgproc.contourArea(wrapper);
if (area > maxArea) {
maxArea = area;
}
}
Mat result=null;
each = contours.iterator();
while (each.hasNext()) {
MatOfPoint contour = each.next();
double area = Imgproc.contourArea(contour);
if (area > 0.01 * maxArea) {
// 多边形逼近 会使原图放大4倍
Core.multiply(contour, new Scalar(4, 4), contour);
MatOfPoint2f newcoutour = new MatOfPoint2f(contour.toArray());
MatOfPoint2f resultcoutour = new MatOfPoint2f();
double length = Imgproc.arcLength(newcoutour, true);
Double epsilon = 0.01 * length;
Imgproc.approxPolyDP(newcoutour, resultcoutour, epsilon, true);
contour = new MatOfPoint(resultcoutour.toArray());
// 进行修正,缩小4倍改变联通区域大小
MatOfPoint new_contour=new MatOfPoint();
new_contour=ChangeSize(contour);
double new_area = Imgproc.contourArea(new_contour);//轮廓的面积
//最小外接矩形
Rect rect = Imgproc.boundingRect(new_contour);
double rectarea = rect.area();//最小外接矩形面积
if (Math.abs((new_area/rectarea)-1)<0.2){
// Log.e("PlateDetector", "车牌宽度 "+rect.width);
if (rect.width > 300) {
Mat imgSource=new_src.clone();
Imgproc.rectangle(imgSource, rect.tl(), rect.br(),
new Scalar(255, 0, 0), 2);
if(temp==1){
//蓝色车牌裁剪范围**************88
// rect.x+=8;
rect.x+=65;
// rect.width-=15;
rect.width-=69;
rect.y+=8;
rect.height-=15;
//蓝色车牌裁剪范围****************8888
}
if (temp==2){
//绿色车牌裁剪范围**************
rect.x+=97;
rect.width-=109;
rect.y+=8;
rect.height-=15;
//绿色车牌裁剪范围**************
}
result=new Mat(imgSource,rect);
Mat gray=new Mat();
Imgproc.cvtColor(result,gray,Imgproc.COLOR_BGR2GRAY);//灰度化
//字体黑色时,要反色
if (temp==2){
Core.bitwise_not(gray,gray);//绿色的,要取反(因为绿色中间的字是黑色的) 蓝色的不用(因为蓝色中间的字是白色的)
}
Mat threshold=new Mat();
//蓝色自适应阈值
if (temp==1){
Imgproc.adaptiveThreshold(gray,threshold,255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY,111,-7);
Mat kernel=Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE,new Size(3,3));
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_ERODE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_ERODE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_DILATE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_DILATE,kernel);
}
//绿色自适应阈值
if (temp==2) {
Imgproc.adaptiveThreshold(gray,threshold,255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY,111,-7);
Mat kernel=Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE,new Size(3,3));
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_ERODE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_ERODE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_ERODE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_ERODE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_DILATE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_DILATE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_DILATE,kernel);
Imgproc.morphologyEx(threshold,threshold,Imgproc.MORPH_DILATE,kernel);
}
Bitmap threshold_bitmap=Mat2Bitmap(threshold);
String tess_str=doOcr(threshold_bitmap,"eng");//num4 和num6比较准确
Log.e("PlateDetector", "车牌识别结果 "+tess_str);
if (tess_str!=null){
if(tess_str.length()>=6&&tess_str.length()<=10){
result_str=plateString(tess_str);
String result_str1=result_str;
Log.i("PlateDetector", "车牌处理后结果: "+result_str);
return result_str1;
}
}
}
}
}
}
return result_str;
}
//车牌发送给道闸
public static void plateToGate(String plateResult){
if (plateResult!=null&&plateResult.length()==6){
Log.i(TAG, "正在发送车牌识别结果1:"+plateResult);
FirstActivity.Connect_Transport.yanchi(100);
FirstActivity.Connect_Transport.gate(0x10, plateResult.charAt(0), plateResult.charAt(1), plateResult.charAt(2));
FirstActivity.Connect_Transport.yanchi(100);//多发几次防止数据丢失
FirstActivity.Connect_Transport.gate(0x10, plateResult.charAt(0), plateResult.charAt(1), plateResult.charAt(2));
FirstActivity.Connect_Transport.yanchi(100);//多发几次防止数据丢失
FirstActivity.Connect_Transport.gate(0x10, plateResult.charAt(0), plateResult.charAt(1), plateResult.charAt(2));
FirstActivity.Connect_Transport.yanchi(100);
FirstActivity.Connect_Transport.gate(0x11, plateResult.charAt(3), plateResult.charAt(4), plateResult.charAt(5));
FirstActivity.Connect_Transport.yanchi(100);
FirstActivity.Connect_Transport.gate(0x11, plateResult.charAt(3), plateResult.charAt(4), plateResult.charAt(5));
FirstActivity.Connect_Transport.yanchi(100);
FirstActivity.Connect_Transport.gate(0x11, plateResult.charAt(3), plateResult.charAt(4), plateResult.charAt(5));
FirstActivity.Connect_Transport.yanchi(100);
Log.i(TAG, "正在发送车牌识别结果2:"+plateResult);
}
}
/**
* 车牌字符串处理
*将传入的车牌字符串进行识别
*
* */
private String plateString(String plateResult){
//后面的六位字符toLowerCase() 大写转小写
try {
plateResult=plateResult.replaceAll(" ","");
//后面的六位字符toUpperCase() 小写转大写
String platNumber = plateResult.substring(plateResult.length() - 6, plateResult.length()).toUpperCase();
StringBuilder strBuilder = new StringBuilder(platNumber);
strBuilder=plateReplace(strBuilder);//按照A123B4 的格式 进行相似匹配替换
platNumber=strBuilder.toString();
plateResult=platNumber;
}catch (StringIndexOutOfBoundsException e1){
e1.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
return plateResult;
}
/**
* 车牌数据替换,减少错误率
*
*/
private StringBuilder plateReplace(StringBuilder platNumber){
if(platNumber.charAt(1)>='A'&&platNumber.charAt(1)<='Z'){
platNumber=charToNum(platNumber,1);//第1、2、3、5位本应该为数字,如果出现识别为字符就将其转换为数字
}
if(platNumber.charAt(2)>='A'&&platNumber.charAt(2)<='Z'){
platNumber=charToNum(platNumber,2);//第1、2、3、5位本应该为数字,如果出现识别为字符就将其转换为数字
}
if(platNumber.charAt(3)>='A'&&platNumber.charAt(3)<='Z'){
platNumber=charToNum(platNumber,3);//第1、2、3、5位本应该为数字,如果出现识别为字符就将其转换为数字
}
if(platNumber.charAt(5)>='A'&&platNumber.charAt(5)<='Z'){
platNumber=charToNum(platNumber,5);//第1、2、3、5位本应该为数字,如果出现识别为字符就将其转换为数字
}
if (platNumber.charAt(0)>='0'&&platNumber.charAt(0)<='9'){
platNumber=numToChar(platNumber,0);//第0、4位本应该为字符,如果出现识别为数字就将其转换为字符
}
if (platNumber.charAt(4)>='0'&&platNumber.charAt(4)<='9'){
platNumber=numToChar(platNumber,4);//第0、4位本应该为字符,如果出现识别为数字就将其转换为字符
}
// platNumber=numToChar(platNumber,4);
// sleep(10);
return platNumber;
}
/**
* 车牌中的数字转换为字符
* 车牌:H833E8 位置:i=(0)、(4)
*/
private StringBuilder numToChar(StringBuilder platNumber,int i){
char a=platNumber.charAt(i);
switch (a){
case '0':
platNumber.setCharAt(i,'D');
break;
case '1':
platNumber.setCharAt(i,'I');
break;
case '2':
platNumber.setCharAt(i,'Z');
break;
case '3':
platNumber.setCharAt(i,'B');
break;
case '4':
platNumber.setCharAt(i,'A');
break;
case '5':
platNumber.setCharAt(i,'S');
break;
case '6':
platNumber.setCharAt(i,'G');
break;
case '7':
platNumber.setCharAt(i,'T');
break;
case '8':
platNumber.setCharAt(i,'B');
break;
case '9':
break;
default:
break;
}
return platNumber;
}
/**
* 车牌中字符转换为数字
* 车牌:H833E8 位置:i=(1 2 3)、(5)
*/
private StringBuilder charToNum(StringBuilder platNumber,int i){
char a=platNumber.charAt(i);
switch (a){
case 'A':
platNumber.setCharAt(i,'4');
break;
case 'B':
platNumber.setCharAt(i,'8');
break;
case 'C':
platNumber.setCharAt(i,'0');
break;
case 'D':
platNumber.setCharAt(i,'4');
break;
case 'E':
break;
case 'F':
break;
case 'G':
platNumber.setCharAt(i,'6');
break;
case 'H':
break;
case 'I':
platNumber.setCharAt(i,'1');
break;
case 'J':
break;
case 'K':
break;
case 'L':
platNumber.setCharAt(i,'1');
break;
case 'M':
break;
case 'N':
break;
case 'O':
platNumber.setCharAt(i,'0');
break;
case 'P':
break;
case 'Q':
break;
case 'R':
break;
case 'S':
platNumber.setCharAt(i,'5');
break;
case 'T':
platNumber.setCharAt(i,'7');
break;
case 'U':
break;
case 'V':
break;
case 'W':
break;
case 'X':
break;
case 'Y':
break;
case 'Z':
platNumber.setCharAt(i,'2');
break;
case '?':
platNumber.setCharAt(i,'9');
break;
}
return platNumber;
}
/**
* 进行图片识别
*
* @param bitmap 待识别图片
* @param language 识别语言
* @return 识别结果字符串
*/
public String doOcr(Bitmap bitmap, String language) {
// if (language == null)
// language = "eng";
TessBaseAPI baseApi = new TessBaseAPI();
FirstActivity firstActivity=new FirstActivity();
// 必须加此行,tess-two要求BMP必须为此配置
Log.e("语言包路径", firstActivity.getSDPath());
baseApi.init(firstActivity.getSDPath(), language);
//白名单
baseApi.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
// 黑名單
baseApi.setVariable(TessBaseAPI.VAR_CHAR_BLACKLIST, "[email protected]#$%^&*()_+=-[]}{;:'\"\\|~`,./<>?' 'abcdefghijklmnopqrstuvwxyz");
System.gc();
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
baseApi.setImage(bitmap);
String text = baseApi.getUTF8Text();
baseApi.clear();
baseApi.end();
return text;
}
// 转换工具
public static Bitmap Mat2Bitmap(Mat cannyMat) {
Bitmap bmpCanny = Bitmap.createBitmap(cannyMat.cols(), cannyMat.rows(),
Bitmap.Config.ARGB_8888);
Utils.matToBitmap(cannyMat, bmpCanny);
return bmpCanny;
}
// 转换工具
public static Mat Bitmap2Mat(Bitmap bmp) {
Mat mat = new Mat(bmp.getHeight(), bmp.getWidth(), CvType.CV_8UC4);
Utils.bitmapToMat(bmp, mat);
return mat;
}
// 把坐标降低到4分之一
MatOfPoint ChangeSize(MatOfPoint contour) {
for (int i = 0; i < contour.height(); i++) {
double[] p = contour.get(i, 0);
p[0] = p[0] / 4;
p[1] = p[1] / 4;
contour.put(i, 0, p);
}
return contour;
}
看不懂的就仔细看看。。。。然后再不断地测试