前言
最近一系列的文章都是用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-