专栏首页微卡智享Android通过OpenCV和TesserartOCR实时进行识别

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库为

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

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

TesseratCallBack

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

VaccaeTesserat

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

核心代码

    @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识别

native-lib.cpp

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

图像处理核心方法

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

核心代码

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

//
// 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-

本文分享自微信公众号 - 微卡智享(VaccaeShare),作者:Vaccae

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Delphi使用NativeXml处理XML(二)

    4.1.类(Classes) 4.1.1.TComponentAccess类   TComponentAccess = class(TComponent) 4....

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

    原来做了一个工业项目,关于石英石的深加工控件系统,做为工控机需要与三菱的PLC进行交互操作,包括读取PLC中缓存库的库存,点击加工告诉PLC从哪一个库存里面拿多...

    Vaccae
  • Android NDK编程(八)--- JNI中List结构的类数据做为参数

    上一篇文章我们介绍了《Android NDK编程(七)--- JNI中List结构的类数据返回》,这章主要介绍把List结构的类做为参数在方法中进行实现。

    Vaccae
  • 清北集训Day6T1(生成函数)

    听rqy说可以用生成函数做,感觉比较有意思 我们考虑在DP转移的时候, $5,7,9$这三个数是没有限制的 因此他们出现的次数用01串表示的话就是$111111...

    attack
  • Python切分图像小案例(1、3、2、4象限子图互换)

    首先解释上一篇文章详解Python科学计算扩展库numpy中的矩阵运算(1)最后的习题,该问题答案是10 ** 8 = 100000000,原因在于Python...

    Python小屋屋主
  • 检查.NET程序平台目标(Platform Target)工具CorFlags

    .NET Framework SDK中的一个工具程序: CorFlags.exe。CorFlags.exe不但可查询.NET组件的平台目标设定,甚至能直接修改设...

    张善友
  • 数据分析 ——— pandas可视化(六)

    这篇文章我们进行pandas可视化化的操作, 在这里我只是简单画几个图,表面pandas也是可以用来画图的,后期会在更新matlab等数据可视化的python库...

    andrew_a
  • Python编程一定要注意的那些“坑”(七)

    已发“坑”列表:Python函数默认值参数的2个坑,Python编程中一定要注意的那些“坑”(一),Python编程中一定要注意的那些“坑”(二),Python...

    Python小屋屋主
  • 简单易学的机器学习算法——神经网络之BP神经网络

        BP神经网络是一种多层的前馈神经网络,其主要的特点是:信号是前向传播的,而误差是反向传播的。具体来说,对于如下的只含一个隐层的神经网络模型:

    zhaozhiyong
  • 上海交通大学教授金耀辉:AI在智慧法院中的应用

    11月10日,由上海大数据联盟、数据猿主办,上海科睿联合主办的《构建智慧法院,促进司法职能—魔方大数据》在上海超级计算中心举行。本文是数据猿整理的“上海交通大学...

    数据猿

扫码关注云+社区

领取腾讯云代金券