Android通过OpenCV和TesserartOCR实时进行识别

前言

最近一系列的文章都是用Android利用OpenCV NDK的方法通过摄像头实时获取图像进行图像处理,在上一篇《Android使用Tesseract-ocr进行文字识别》我们学习了一下TesserartOCR的图像识别功能,这一章主要介绍怎么样通过图像的处理再加上我们OCR的识别获取的想要的东西。

提前说了下,OpenCV我个人还是个小白阶段,原来的数据处理是想提取车牌信息再通过OCR把车牌识别出来,不过确实差强人意,不过我们整个程序的基本框架算是都完成了,只不过最后在OpenCV里的车牌定位什么的可能需要自己研究吧。

视频效果

代码实现

主框架

程序的主框架还是用《Android利用SurfaceView显示Camera图像爬坑记(六) -- 用OpenCV进行Canny边缘检测》里面的那套,我们重新建了一个新的项目,OpenCV还有NDK的设置都是按SurfaceView调用Camera的方式进行处理的。

TesserartOCR配置

Android使用Tesseract-ocr进行文字识别》中我们通过导入Tess-Two这个Module后进行处理的,但是这个每次重新编译都要十几分钟,原理上它还是用的NDK方式,所以我们直接把Tess-Two编译好的so库用在这里,就不再引入这个Module了,用到的4个so库为

format,png

我们直接把这几个动态库放入到和Opencv相关的目录下,对应的不同的arm拷入,如下图

format,png

format,png

上面对应的so库放到一起后,我们在build.gradle中要加入这个的引入,如下图:

format,png

TesseratCallBack

为了不影响程序的流畅度,我们的OCR识别都是在线程中操作,这个接口是用于OCR识别后的文字通过这个回调函数接口传给主进程中。

format,png

VaccaeTesserat

这个类用的AsyncTask用于进行OCR的识别。

format,png

核心代码

    @Override
    protected String doInBackground(Bitmap... bitmaps) {
        TessBaseAPI tessAPI=null;
        try {


            StringBuilder sb=new StringBuilder();
            // 核心预设置代码
            tessAPI=new TessBaseAPI();
            //如果Android的版本大于23,路径取根目录下的tesserart,小于的话是
            //在mnt/sdcard下面
            String path=Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "tesserart";
            tessAPI.setDebug(true);
            tessAPI.init(path , "eng");
//            tessAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_AUTO);
            tessAPI.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
            tessAPI.setVariable(TessBaseAPI.VAR_CHAR_BLACKLIST, "!@#$%^&*()_+=-[]}{;:'\"\\|~`,./<>?");


            //第一张为原图不取
            for (int i=1; i < bitmaps.length; i++) {
                tessAPI.setImage(bitmaps[i]);
                // 获取并显示识别结果
                sb.append(tessAPI.getUTF8Text());
            }


            mCallBack.CallBackOver(sb.toString());
        } catch (Exception e) {
            Log.e("Tess", e.getMessage());
            mCallBack.CallBackOver(e.getMessage());
        } finally {
            tessAPI.clear();
            tessAPI.end();
        }


        return null;
    }

VaccaeOpenCVJNI

jni的方法里面定义了获取Cameraframe实时帧的图像,返回是Bitmap的列表,第一个还是原图用于显示,后面的就是我们截取的判断为车牌的矩形图用于OCR识别

format,png

native-lib.cpp

这里是JNI方法中的实现方法,主要是怎么将bitmap转为OpenCV中的Mat,和图像处理结束后怎么再生成List<Bitmap>,下图右边红框中就是图像处理的核心方法,这个我们写在了testcv的C++文件中。

format,png

图像处理核心方法

核心方法我们自己新建了一个C++的类,生成了testcv的头文件和源文件。

format,png

核心代码

这里面是我们查找类似车牌的处理方法,部分是参考网上的定位方法。

//
// Created by 36574 on 2019-06-25.
//


#include "testcv.h"




bool testcv::VerifySize(RotatedRect candidate) {
    float error = 0.2; //20%的误差范围
    float aspect = 4.7272;//宽高比例
    int min = 15 * aspect * 15; //最小像素为15
    int max = 125 * aspect * 125;//最大像素为125
    float rmin = aspect - aspect * error;//最小误差
    float rmax = aspect + aspect * error;//最大误差
    int area = candidate.size.height * candidate.size.width;//求面积
    float r = (float) candidate.size.width / (float) candidate.size.height;//长宽比
    if (r < 1) r = 1 / r;
    if (area < min || area > max || r < rmin || r > rmax
        || abs(candidate.angle) > 10 || candidate.size.width < candidate.size.height) {
        return false;
    } else {
        return true;
    }
}


//获取多个截取的矩形
std::vector<Mat> testcv::getrectdetector(Mat &src) {
    Mat gray, imgsobel, dst;
    //转为灰度图
    cvtColor(src, gray, cv::COLOR_BGRA2GRAY);
    //高斯模糊
    GaussianBlur(gray, gray, Size(5, 5), 0.5, 0.5);
    //利用sobel滤波,对x进行求导,就是强调Y方向
    Sobel(gray, imgsobel, CV_8U, 1, 0, 3);
    //二值化
    threshold(imgsobel, imgsobel, 0, 255, THRESH_BINARY | THRESH_OTSU);
    //闭操作  这个Size很重要
    Mat element = getStructuringElement(MORPH_RECT, Size(21, 5));
    morphologyEx(imgsobel, imgsobel, MORPH_CLOSE, element);


    //提取轮廓
    std::vector<std::vector<cv::Point>> contours;
    findContours(imgsobel, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);


    //用来存放旋转矩形的容器
    std::vector<RotatedRect> Rotatedrects;


    //判断图像
    for (size_t i = 0; i < contours.size(); i++) {
        //用来存放旋转矩形4个点
        Point2f Vertices[4];
        //寻找最小矩形
        RotatedRect currentrect = minAreaRect(Mat(contours[i]));
        //判断是不是要找的区域,如果是画线
        if (VerifySize(currentrect)) {
            currentrect.points(Vertices);
            //在源图上画四点的线
            for (size_t j = 0; j < 4; j++) {
                line(src, Vertices[j], Vertices[(j + 1) % 4], Scalar(0, 0, 255),
                     3);
            }
            //将符合的矩形存放到容器里
            Rotatedrects.push_back(currentrect);
        }
    }


    //用于存放识别到的图像
    std::vector<Mat>output;
    for (size_t i = 0; i < Rotatedrects.size(); i++) {
        Mat dst_warp;
        Mat dst_warp_rotate;
        Mat rotMat(2, 3, CV_32FC1);
        dst_warp = Mat::zeros(src.size(), src.type());
        float r = (float)Rotatedrects[i].size.width / (float)Rotatedrects[i].size.height;
        float  angle = Rotatedrects[i].angle;
        if (r < 1)
            angle = angle + 90;


        //其中的angle参数,正值表示逆时针旋转,关于旋转矩形的角度,以为哪个是长哪个是宽,在下面会说到
        rotMat = getRotationMatrix2D(Rotatedrects[i].center,angle, 1);
        //将矩形通过仿射变换修正回来
        warpAffine(src, dst_warp_rotate, rotMat, dst_warp.size());


        Size rect_size = Rotatedrects[i].size;
        if (r < 1)
            swap(rect_size.width, rect_size.height);


        //定义输出的图像
        Mat dst(Rotatedrects[i].size, CV_8U);
        //裁剪矩形,下面的函数只支持CV_8U 或者CV_32F格式的图像输入输出。
        //所以要先转换图像将RGBA改为RGB
        cvtColor(dst_warp_rotate, dst_warp_rotate, CV_RGBA2RGB);
        //裁剪矩形
        getRectSubPix(dst_warp_rotate, rect_size, Rotatedrects[i].center, dst);


        //将裁减到的矩形设置为相同大小,并且提高对比度
        Mat resultResized;
        resultResized.create(33, 144, CV_8UC3);
        resize(dst, resultResized, resultResized.size(), 0, 0, INTER_CUBIC);
        Mat grayResult;
        cvtColor(resultResized, grayResult, CV_BGR2GRAY);
        blur(grayResult, grayResult, Size(3, 3));
        //均值化提高对比度
        equalizeHist(grayResult, grayResult);


        //最终生成的矩形存放进vector<Mat>中
        output.push_back(grayResult);
    }


    return output;
}



总结及源码下载地址

项目中定位车牌的效果很一般,主要是自己也是OpenCV的初学者,个项目的主要目的是为了搭建出可以OpenCV及TesserartOCR的整个NDK的框架。

下载地址

GitHub:https://github.com/Vaccae/AndroidOpenCVTesserartOCR.git

-END-

Vaccae的往期经典


OpenCV

《C++ OpenCV案例实战---卡号获取

《C++ OpenCV案例实战---卡片截取(附代码)

《C++ OpenCV透视变换---切换手机正面图片》

《C++ OpenCV实战---获取数量

《C++ OpenCV实战---利用颜色分割获取数量》

《OpenCV4Android NDK方式进行Canny边缘检测》


Android

《Android利用SurfaceView结合科大讯飞修改语音实别UI

《Android关于语音识别的功能实现分析(一)---结构化思维》

《Android关于语音识别的功能实现分析(二)---语义解析》

《Android根据类生成签名字符串

《Android碎片化布局fragment的实战应用

《Android中RecyclerView嵌套RecyclerView

《Android里用AsyncTask后的接口回调


.Net C#

《C#自定义特性(Attribute)讲解与实际应用

《C#根据类生成签名字符串(附DEMO下载地址)

《C++创建动态库C#调用》

《C#与三菱PLC(型号FX2N)串口通讯类


数据库及其它

《Oracel存储过程写报表实战》

《Delphi轮播视频和图片程序(用于双屏显示程序)

《SQL随机增加销售数据的脚本编写(附脚本下载地址)

SQL Server中With As的介绍与应用(三)--递归的实战应用

《Oracle通过ODBC连接SQL Server数据库

Oracle利用row_number()over()方式解决插入数据时重复键的问题


长按下方二维码关注微卡智享

format,png

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值