当前位置:网站首页>Android NDK 开发实战 - 微信公众号二维码检测
Android NDK 开发实战 - 微信公众号二维码检测
2020-11-09 12:12:00 【osc_tei3s15d】
关于二维码识别,我们一般都是用的 Zxing 或者 Zbar ,但它们的识别率其实不是很高,有些情况下是失灵的,比如下面这两张图:
使用开源库 Zxing 扫描以上两张二维码,有一张死活不识别。使用微信是可以的,大家可以用支付宝试试(不行),那碰到这种情况到底该怎么办呢?哈哈,这次终于有用武之地了,我们琢磨着来优化一把。
我们在微信公众号都用过这么一个功能,长按一张图片,如果该图片包含有二维码,会弹出识别图中二维码,如果该图片不含有二维码,则不会弹出识别二维码这个选项。说到这里我们大概应该知晓了,识别二维码其实分为两步,第一步是发现截取二维码区域,第二步是识别截取到的二维码区域。那么 zxing 和支付宝到底是哪一步出了问题呢?首先我们来看一下第一步发现截取二维码区域。
二维码事例
上图是一张常用的二维码事例图,有三个比较重要的区域,分别是左上,右上和左下,我们只要能找到这三个特定的区域,就能判定图片中包含有二维码。接下来我们来分析一下思路:
1. 对其进行轮廓查找
2. 对查找的到的轮廓进行初步过滤
3. 判断是否符合二维码的特征规则
4. 截取二维码区域
5. 识别二维码
// 判断 X 方向上是否符合规则
bool isXVerify(const Mat& qrROI){
... 代码省略
// 判断 x 方向从左到右的像素比例
// 黑:白:黑:白:黑 = 1:1:3:1:1
}
// 判断 Y 方向上是否符合规则
bool isYVerify(const Mat& qrROI){
... 代码省略
// y 方向上也可以按照 isXVerify 方法判断
// 但我们也可以适当的写简单一些
// 白色像素 * 2 < 黑色像素 && 黑色像 < 4 * 白色像素
}
int main(){
Mat src = imread("C:/Users/hcDarren/Desktop/android/code1.png");
if (!src.data){
printf("imread error!");
return -1;
}
imshow("src", src);
// 对图像进行灰度转换
Mat gary;
cvtColor(src, gary, COLOR_BGR2GRAY);
// 二值化
threshold(gary, gary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("threshold", gary);
// 1. 对其进行轮廓查找
vector<vector<Point> > contours;
findContours(gary, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
// 2. 对查找的到的轮廓进行初步过滤
double area = contourArea(contours[i]);
// 2.1 初步过滤面积 7*7 = 49
if (area < 49){
continue;
}
RotatedRect rRect = minAreaRect(contours[i]);
float w = rRect.size.width;
float h = rRect.size.height;
float ratio = min(w, h) / max(w, h);
// 2.2 初步过滤宽高比大小
if (ratio > 0.9 && w< gary.cols/2 && h< gary.rows/2){
Mat qrROI = warpTransfrom(gary, rRect);
// 3. 判断是否符合二维码的特征规则
if (isYVerify(qrROI) && isXVerify(qrROI)) {
drawContours(src, contours, i, Scalar(0, 0, 255), 4);
}
}
}
imshow("src", src);
imwrite("C:/Users/hcDarren/Desktop/android/code_result.jpg", src);
waitKey(0);
return 0;
}
处理结果
代码是非常简单的,关键是我们要善于学会去分析,多多培养解决问题的能力,只要知道实现思路,其他一切都不是问题了。那么有意思的就来了,当扫描第二张图的时候,我们发现死活都识别不了。那么细心的同学可能明白了,我们上面的代码是按照正方形的特征来识别的,而第二张图是圆形的特征,因此 Zxing 无法识别也是正常的,因为咱们在写代码的时候根本没考虑这么个情况。那么我们怎么才能做到识别圆形的特征呢?考验我们的时候到了,我们能想到三种解决方案:
1. 再写一套识别圆形特征的代码
2. 借鉴人脸识别的方案,采用训练样本的方式识别
3. 换一种检查方案,只写一套代码
人脸识别在下期文章中会写到,训练样本的方式比较麻烦,如果之前没接触过,那么需要一定的时间成本,但这种方案应该是最好的。再写一套圆形识别的代码,感觉维护困难,作为一个有灵魂的工程师总觉得别扭。那这里我们就采用第三种方案了,其实知识点也就那么多,还是那句话多培养我们分析解决问题的能力。
我们仔细观察,他们其实还是有很多共同点,我们对其进行轮廓筛选的时候会发现,都是一个大轮廓里面套两个小轮廓。具体流程如下:
1. 对其进行轮廓查找
2. 对查找的到的轮廓进行初步过滤
3. 判断是否是一个大轮廓套两个小轮廓且符合特征规则(面积比例判断)
4. 截取二维码区域
5. 识别二维码
extern "C"
JNIEXPORT jobject JNICALL
Java_com_darren_ndk_day76_MainActivity_clipQrBitmap(JNIEnv *env, jobject instance, jobject bitmap) {
Mat src;
cv_helper::bitmap2mat(env, bitmap, src);
// 对图像进行灰度转换
Mat gary;
cvtColor(src, gary, COLOR_BGR2GRAY);
// 二值化
threshold(gary, gary, 0, 255, THRESH_BINARY | THRESH_OTSU);
// 1. 对其进行轮廓查找
vector<Vec4i> hierarchy;
vector<vector<Point> > contours;
vector<vector<Point> > contoursRes;
/*
参数说明:https://blog.csdn.net/guduruyu/article/details/69220296
输入图像image必须为一个2值单通道图像
contours参数为检测的轮廓数组,每一个轮廓用一个point类型的vector表示
hiararchy参数和轮廓个数相同,每个轮廓contours[ i ]对应4个hierarchy元素hierarchy[ i ][ 0 ] ~hierarchy[ i ][ 3 ],
分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,该值设置为负数。
mode表示轮廓的检索模式
CV_RETR_EXTERNAL 表示只检测外轮廓
CV_RETR_LIST 检测的轮廓不建立等级关系
CV_RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
CV_RETR_TREE 建立一个等级树结构的轮廓。具体参考contours.c这个demo
method为轮廓的近似办法
CV_CHAIN_APPROX_NONE 存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
CV_CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS 使用teh-Chinl chain 近似算法
offset表示代表轮廓点的偏移量,可以设置为任意值。对ROI图像中找出的轮廓,并要在整个图像中进行分析时,这个参数还是很有用的。
*/
findContours(gary, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));
int tCC = 0; // 临时用来累加的子轮廓计数器
int pId = -1;// 父轮廓的 index
for (int i = 0; i < contours.size(); i++) {
if (hierarchy[i][2] != -1 && tCC == 0) {
pId = i;
tCC++;
} else if (hierarchy[i][2] != -1) {// 有父轮廓
tCC++;
} else if (hierarchy[i][2] == -1) {// 没有父轮廓
tCC = 0;
pId = -1;
}
// 找到了两个子轮廓
if (tCC >= 2) {
contoursRes.push_back(contours[pId]);
tCC = 0;
pId = -1;
}
}
// 找到过多的符合特征轮廓,对其进行筛选
if (contoursRes.size() > FEATURE_NUMBER) {
contoursRes = filterContours(gary, contoursRes);
}
// 没有找到符合的条件
if (contoursRes.size() < FEATURE_NUMBER) {
return NULL;
}
for (int i = 0; i < contoursRes.size(); ++i) {
drawContours(src, contoursRes, i, Scalar(255, 0, 0), 2);
}
// 裁剪二维码,交给 zxing 或者 zbar 处理即可
cv_helper::mat2bitmap(env, src, bitmap);
return bitmap;
}
处理结果
开发中我们最喜欢做的就是拿过来直接用,但最好还是明白其中的原理,因为我们无法断定开发中会出什么幺蛾子。像微信这样的大厂自然得自己这一套,其实好的框架能够拿过来优化优化,个人认为就已经差不多了。当然以上写法在某些特定场景下,可能还是会存在些许漏洞,这就靠我们不断的去琢磨优化了。
PS:关于我
本人是一个拥有6年开发经验的帅气Android攻城狮,记得看完点赞,养成习惯,微信搜一搜「 程序猿养成中心 」关注这个喜欢写干货的程序员。
另外耗时两年整理收集的Android一线大厂面试完整考点PDF出炉,资料【完整版】已更新在我的【Github】,有面试需要的朋友们可以去参考参考,如果对你有帮助,可以点个Star哦!
版权声明
本文为[osc_tei3s15d]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4293376/blog/4709319
边栏推荐
- 接口测试如何在post请求中传递文件
- 050_ object-oriented
- 利用 Python 一键下载网易云音乐 10W+ 乐库
- Mac terminal oh my Zsh + solarized configuration
- SQL statement to achieve the number of daffodils
- Method of creating flat panel simulator by Android studio
- Python零基础入门教程(01)
- Reread reconstruction
- 零基础IM开发入门(四):什么是IM系统的消息时序一致性?
- Mapstructure detoxifies object mapping
猜你喜欢
Wechat circle
050_ object-oriented
inet_pton()和inet_ntop()函数详解
Android Development - service application, timer implementation (thread + service)
PAT_甲级_1074 Reversing Linked List
在嵌入式设备中实现webrtc的第三种方式③
Stack & queue (go) of data structure and algorithm series
The middle stage of vivo Monkey King activity
Download Netease cloud music 10W + music library with Python
线上服务的FGC问题排查,看这篇就够了!
随机推荐
如何保证消息不被重复消费?(如何保证消息消费的幂等性)
As a user, you can't get rid of the portrait!
SQL statement to achieve the number of daffodils
通配符SSL证书应该去哪里注册申请
“开源软件供应链点亮计划 - 暑期 2020”公布结果 基于 ChubaoFS 开发的项目获得最佳质量奖
PAT_甲级_1074 Reversing Linked List
Glsb involves load balancing algorithm
分库分表的 9种分布式主键ID 生成方案,挺全乎的
After SQL group query, get the first n records of each group
Android check box and echo
Interview summary on November 7, 2020 (interview 12K)
使用rem,做到屏幕缩放时,字体大小随之改变
开源 | HMGNN:异构小图神经网络及其在拉新裂变风控场景的应用
10款必装软件,让Windows使用效率飞起!
How to query by page after 10 billion level data is divided into tables?
inet_pton()和inet_ntop()函数详解
走进京东 | 中国空间技术研究院青年创新联盟成员莅临参观京东总部
The third way to realize webrtc in embedded devices
Android 复选框 以及回显
android studio创建平板模拟器方法