当前位置:网站首页>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
边栏推荐
- 微信圈子
- 使用流读文件写文件处理大文件
- Explain Python input() function: get user input string
- EFF 认为 RIAA 正在“滥用 DMCA”来关闭 YouTube-DL
- A simple ability determines whether you will learn!
- 向北京集结!OpenI/O 2020启智开发者大会进入倒计时
- 走进京东 | 中国空间技术研究院青年创新联盟成员莅临参观京东总部
- Introduction to zero based im development (4): what is message timing consistency in IM systems?
- 开源ERP招聘了
- Android Development - service application, timer implementation (thread + service)
猜你喜欢
在企业的降本增效诉求下,Cube如何助力科盾业务容器化“一步到位”?
Jsliang job series - 08 - handwritten promise
How to query by page after 10 billion level data is divided into tables?
android studio创建平板模拟器方法
Pay attention to the request forwarding problem of. Net core
Impact of libssl on CentOS login
Depth analysis based on synchronized lock
Mac 终端(terminal) oh-my-zsh+solarized配置
The choice of domain name of foreign trade self built website
Handwritten digital image recognition convolution neural network
随机推荐
Android权限大全
In the future, China Telecom will make cloud computing service the main business of China Telecom
Aren't you curious about how the CPU performs tasks?
手写Koa.js源码
开源 | HMGNN:异构小图神经网络及其在拉新裂变风控场景的应用
苏宁基于知识图谱的大规模告警收敛和根因定位实践
Mac 必备优质工具推荐
Jsliang job series - 08 - handwritten promise
SEO见风使舵,是对还是错?
线上服务的FGC问题排查,看这篇就够了!
Kubernetes业务日志收集与监控
Python zero basics tutorial (01)
医疗项目管理的三种实用技巧
The middle stage of vivo Monkey King activity
How to ensure that messages are not consumed repeatedly? (how to ensure the idempotent of message consumption)
用一种简单的方式实现终端文字粘贴板
inet_pton()和inet_ntop()函数详解
Safety (miscellany)
From coding, network transmission, architecture design, Tencent cloud high quality, high availability real-time audio and video technology practice
Reading design patterns adapter patterns