Android+Opencv+Tesseract-ocr识别不同底色的车牌,蓝色,绿色(新能源)车牌

视频效果: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;
    }

看不懂的就仔细看看。。。。然后再不断地测试

  • 9
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值