当前位置:网站首页>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
边栏推荐
- Kubernetes业务日志收集与监控
- Mac 终端(terminal) oh-my-zsh+solarized配置
- Method of creating flat panel simulator by Android studio
- inet_ Pton () and INET_ Detailed explanation of ntop() function
- 再读《重构》
- 外贸自建网站域名的选择— Namesilo 域名购买
- 为wget命令设置代理
- 医疗项目管理的三种实用技巧
- SHOW PROFILE分析SQL语句性能开销
- Understanding data structures starts with this article~
猜你喜欢
nodejs学习笔记(慕课网nodejs从零开发web Server博客项目)
开源ERP招聘了
【golang】GC详解
手写Koa.js源码
SHOW PROFILE分析SQL语句性能开销
[design pattern] Chapter 4: Builder mode is not so difficult
Understanding task and async await
TiDB x 微众银行 | 耗时降低 58%,分布式架构助力实现普惠金融
PAT_甲级_1074 Reversing Linked List
The third way to realize webrtc in embedded devices
随机推荐
Depth analysis based on synchronized lock
开源 | HMGNN:异构小图神经网络及其在拉新裂变风控场景的应用
“微服务”技术另一个可能更合适的名字
嗯,查询滑动窗口最大值的这4种方法不错....
Program life: from Internet addicts to Microsoft, bat and byte offer harvesters
Large scale project Objective-C - nsurlsession access SMS verification code application example sharing
Android check box and echo
libssl对CentOS登录的影响
Review of hot spots of last week (11.2-11.8)
Android权限大全
Handwritten digital image recognition convolution neural network
美国大选拜登获胜!硅谷的Python开发者用这种方式调侃懂王
用一种简单的方式实现终端文字粘贴板
After SQL group query, get the first n records of each group
为wget命令设置代理
El table dynamic header
Nine kinds of distributed primary key ID generation schemes of sub database and sub table are quite comprehensive
Mac 必备优质工具推荐
Python zero basics tutorial (01)
Recommended tools for Mac