二维码识别率优化实践

news2024/11/28 20:57:45

922b8bd8c647bfeb459c9db3821638a2.jpeg

376fc8f488a2dafaa0d97a7eb312dc3c.gif 

本文字数:5939

预计阅读时间:15 分钟

概述

长按图片识别二维码在移动端是很常见的操作,长按后需要对图片进行识别,并且将二维码中所包含的数据解码出来。在我们的业务场景中,是通过点击图片进入大图预览页面。长按大图预览的图片,会识别图片中的二维码,并且显示有跳转按钮,提示用户可以跳转二维码对应的页面。

但是,在现有业务场景中,要求图片中二维码不能在视觉上占据太大的位置,所以只能以很小的尺寸显示在下面。为了更好的配合公司现有业务,保证对图片中二维码的识别率,所以需要对二维码识别进行优化。

优化方案

0208cc494801f9b04e476661cdedcf77.png

方案总体分为探测和识别两个核心流程,探测流程主要由图像处理算法,以及OpenCV来实现,识别流程主要由系统AVFoundation库的CIDetector来实现。先将二维码所在区域探测出来,随后对这个区域进行识别增强的处理,以实现模糊、较小的二维码的识别。

探测流程

因为不是每一张图片上都有二维码,探测的意义在于,查找图片中是否有二维码,以及二维码在图片中的位置。从而进行后续的针对性处理。以下,任何一步探测有结果,都将进入识别流程中,并且将探测到的位置传给识别方法。

  1. 第一次探测。转灰度图,通过OpenCVcvtColor函数,将四通道的RGBA图片,转换为单通道的灰度图。

  2. 第二次探测。通过算法进行直方图均衡化(非自适应,并且限制对比度),目的是让图片内轮廓清晰。

  3. 第三次探测。通过算法进行伽马变换,目的是增强图像对比度。

  4. 第四次探测。将原始灰度图的下面20%的右半部分,clone到一个新的Mat对象中,并且进行3.5倍的resize。将得到后的灰度大图调用detect进行探测。

  5. 第五次探测。将原始灰度图的下面20%的左半部分,clone到一个新的Mat对象中,并且进行3.5倍的resize。将得到后的灰度大图调用detect进行探测。

  6. 探测结束。在二维码的定位图形、码元等核心信息没有受损的前提下,这时候基本断定这张图片上没有二维码。

需要注意的是,在探测方案中,为什么选取下面左右两边的20%着重进行探测。是因为根据对公司实际业务的调研,绝大多数的二维码都是在图片的右下角位置,其次是左下角。以公司业务为例,目前没见过将二维码放在图片中间的场景。

识别流程

  1. 探测到二维码后,会将当时用来探测的图片,以及探测到的二维码区域传递给识别方法。传递过来的图片,可能是resize后的大图,对这些大图识别率会更高。

  2. 从传递过来的图片上,根据二维码所在区域的坐标系,将二维码所在的部分,重绘到一个新的位图对象上。为了保证识别效果,会在重绘时加上一个15的外边距,以保证二维码Quiet Zone的特性。

  3. 用重绘后的二维码,调用AVFoundationCIDetector进行识别,并获取识别后的字符串。

为什么不把探测和识别都交给OpenCV来做。这是因为经过我的测试,我发现OpenCV的识别率较低,远不如CIDetector的识别率。所以,只将探测部分交给OpenCV,但不让OpenCV去识别二维码。

根据OpenCVdetect函数的源码进行查看,发现OpenCV的探测是基于定位符号进行探测的,分为横向和纵向两个方向进行探测,使用OpenCV进行探测是不错的选择。但是OpenCV识别率并不高,所以用OpenCV进行探测,结合AVFoundation进行识别的方案,是一个比较不错的策略。

代码实现

方案总体代码量较大,这里列出了一些主要方法的代码实现。探测方法内部实现,会进行不同程度的增强扫描和识别。方案中使用了一些OpenCVAPI,可以通过OpenCV官方文档了解下API的定义和调用。

生成灰度图

把传入的图片转为OpenCV可以识别的Mat的灰度图,灰度图色彩通道只有一个,在进行后续计算上,会节省很多性能。随后进入后续的探测部分,探测到二维码后,会将二维码拼接业务参数,并在主线程中返回给调用方。

方法的核心逻辑有两部分,一是将UIImage转为OpenCV类型的Mat对象,二是将四个颜色通道的彩色图,转为单个颜色通道的灰度图。二值图和灰色图是不同的,灰色图是单个通道,每个单位占8位,表示范围是0~255。二值图只有0~1的展示,所以识别速度会相对快一些。

这里简要讲一下颜色通道的概念,CV_8UC4表示RGBA四颜色通道,占用32位空间。同样的,也有三颜色通道RGB,以及两个通道和单通道,两个通道的CV_8UC2我没用过,不知道什么场景下会需要。cvtColor函数是OpenCV中进行颜色空间转换的函数,可以将彩色图修改为灰度图。

CGFloat rows = sourceImage.size.height;
CGFloat cols = sourceImage.size.width;
    
cv::Mat cvMat(rows, cols, CV_8UC4);
CGColorSpaceRef colorSpace = CGImageGetColorSpace(sourceImage.CGImage);
CGContextRef context = CGBitmapContextCreate(cvMat.data,
                                             cols,
                                             rows,
                                             8,
                                             cvMat.step[0],
                                             colorSpace,
                                             kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault);
if (context == NULL) {
    return cvMat;
}
CGContextDrawImage(context, CGRectMake(0, 0, cols, rows), sourceImage.CGImage);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
    
cv::Mat grayMat;
cv::cvtColor(cvMat, grayMat, cv::COLOR_BGR2GRAY);
    
return grayMat;

resize

下面代码是对左下和右下两个部分进行探测的逻辑,代码中scale代表局部放大的倍数。根据公司业务,先从右下角进行放大及探测。放大需要获取到像素的行数和列数,这些Mat提供了对应的API

下面是核心逻辑的梳理。

  1. 通过rowRangecolRange获取到需要增强扫描的像素,例如右下角区域的横排和竖排的像素。

  2. 调用clone将像素取出,并赋值给Mat

  3. 调用resize函数扩大到对应的倍数。

需要注意的是,在下面方法的第二段,对右下角进行了强制识别。是一个容错处理,属于“闭眼识别”。第一步无论是否有探测的结果,都会对右下角进行二维码的识别。因为在测试中,对于非常小的二维码,会出现对于resize后的灰度图,探测不到但是能识别到的情况。所以,增加了右下角没有探测到,但依然进行强制识别的逻辑。

CGFloat scale = 3.5f;

cv::Mat copyMat = grayMat.rowRange(grayMat.rows * 0.8, grayMat.rows).colRange(grayMat.cols * 0.5, grayMat.cols).clone();
cv::Mat resizeMat;
cv::resize(copyMat, resizeMat, cv::Size(grayMat.cols * scale * 0.5, grayMat.rows * 0.2 * scale));
NSString *result = [self qrcodeQRCodeForGrayMat:resizeMat];

/// 闭眼识别
if (!result.length) {
    UIImage *image = [self UIImageFromCVMat:resizeMat];
    result = [self readQRCodeWithImage:image
                      detectorAccuracy:CIDetectorAccuracyHigh];
}

if (!result.length) {
    copyMat = grayMat.rowRange(grayMat.rows * 0.8, grayMat.rows).colRange(grayMat.cols * 0.f, grayMat.cols * 0.5).clone();
    cv::resize(copyMat, resizeMat, cv::Size(grayMat.cols * scale * 0.5, grayMat.rows * 0.2 * scale));
    result = [self qrcodeQRCodeForGrayMat:resizeMat];
}

return result;

识别

识别方法会接收传入的二维码坐标系,裁剪出二维码的位置并做相应的处理,随后交给AVFoundation去识别。当探测不到时,不会进入识别的环节。

当探测到二维码时,可以通过output获取二维码的四个点,顺序是左上、右上、右下、左下,顺时针存储。在原有的四个点外面,加上15个单位的Quiet Zone,随后会将这部分图片裁剪出来,并交给AVFoundation去识别。加入Quiet Zone的原因在于,有Quiet Zone的二维码,识别率会比没有的好。

vector<cv::Point> output;
if (output.empty()) {
    return nil;
}
    
/// 左上
cv::Point point0 = output[0];
/// 右下
cv::Point point2 = output[2];
    
CGRect rect = CGRectMake(point0.x, point0.y, point2.x - point0.x, point2.y - point0.y);
CGRect insetRect = CGRectInset(rect, -15, -15);

/// rect合法性检查
if (insetRect.origin.x > 0 && insetRect.origin.y > 0 && insetRect.size.width > 0 && insetRect.size.height > 0) {
    rect = insetRect;
}

UIImage *image = [self UIImageFromCVMat:grayMat];
image = [self drawInRect:rect sourceImage:image];
NSString *result = [self readQRCodeWithImage:image
                            detectorAccuracy:CIDetectorAccuracyLow];
return result;

Mat转换

iOS中进行OpenCV开发的过程中,UIImageMat的相互转换是经常需要的。UIImageMat在生成灰度图的过程中已经涉及,下面的方法是将Mat转为UIImage。核心逻辑就是将Matdata数据,通过CGImageCreate函数创建CGImage完成的。

CGColorSpaceRef colorSpace;
CGBitmapInfo bitmapInfo;
size_t elemsize = cvMat.elemSize();
if (elemsize == 1) {
    colorSpace = CGColorSpaceCreateDeviceGray();
    bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
}
else {
    colorSpace = CGColorSpaceCreateDeviceRGB();
    bitmapInfo = kCGBitmapByteOrder32Host;
    bitmapInfo |= (elemsize == 4) ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNone;
}
    
NSData *data = [NSData dataWithBytes:cvMat.data length:elemsize * cvMat.total()];
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
    
/// 根据Mat创建CGImage
CGImageRef imageRef = CGImageCreate(cvMat.cols,           // width
                                    cvMat.rows,           // height
                                    8,                    // bits per component
                                    8 * cvMat.elemSize(), // bits per pixel
                                    cvMat.step[0],        // bytesPerRow
                                    colorSpace,           // colorspace
                                    bitmapInfo,           // bitmap info
                                    provider,             // CGDataProviderRef
                                    NULL,                 // decode
                                    false,                // should interpolate
                                    kCGRenderingIntentDefault // intent
                                    );
UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
    
return finalImage;

收益

统计方案

二维码识别率的统计比较困难,因为场景是在长按图片识别二维码,但图片中是否有二维码,我们并不知道。并且,用户长按操作可能是为了保存图片,并非识别二维码。

所以,数据分析通过A/B测的方式进行,通过分桶方式实现,两个桶分别为50%的用户量。通过这种方式,可以得出一个基本准确的识别数据。

收益数据

数据从两个维度来统计,识别速度和识别率,数据如下。

  • 优化方案相对原有方案,识别速度提升6.8%

  • 优化方案相对原有方案,识别率提升12%

识别速度OpenCV表现并不是很明显,因为只是通过灰度图的方式提升了识别速度,但如果到了探测的阶段,对速度还是有一定影响的,收益为正数就很不错了。

识别率提升还是不错的,因为是A/B方案的对比,我们并不知道单个方案的真实识别率。但经过我们自测,运营常用的几个存在复杂二维码的账号中,挨个试二维码的识别,自测识别率为100%

展望

除了灰度图,也考虑过通过OTSU算法将灰度图转为二值图,但是发现二值图在图片清晰度不够的情况下,定位符号比较模糊的位置会被转换为白色。这样,定位符号就会缺一个角,不能构成特定比例,导致识别率出现明显下降。既然二值图的方案都不可行,那就别考虑识别轮廓了,所以就乖乖的用灰度图做识别了。

后续计划加入中值滤波后,再进行OTSU算法做二值化,这样转换后的二值图效果会好很多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/142652.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目管理工具dhtmlxGantt甘特图入门教程(六):dhtmlxGantt的扩展完整列表

dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表&#xff0c;可满足项目管理控件应用程序的所有需求&#xff0c;是最完善的甘特图图表库。 这篇文章给大家讲解dhtmlxGantt的扩展完整列表。 DhtmlxGantt正版试用下载&#xff08;qun&#xff1a;764148812&…

【NI Multisim 14.0原理图环境设置——电路总体设计流程】

目录 序言 &#x1f34a;知识点 一、电路板总体设计流程 &#x1f349; 1.创建电路文件 &#x1f349;2.规划电路界面 &#x1f349;3.放置元器件 &#x1f349;4.连接线路和放置节点 &#x1f349;5.连接仪器仪表 &#x1f349;6. 运行仿真并检查错误 &#x1f349;7…

Dropzone4 for MAC 文件拖拽增强工具

前言 ​​Dropzone for mac是一款文件拖拽操作增强工具&#xff0c;可以让我们把大部分工作都通过拖拽来完成&#xff0c;只需将文件拖拽到菜单栏上的窗口即可。比如保存文本、发送邮件、FTP上传、打开应用等等。提高了用户的工作效率。 下载 Dropzone4 特征 -打开应用程序…

连接格式优化,支持自定义

12月&#xff0c; eKuiper 团队继续专注于 1.8.0 版本新功能的开发。我们重构了外部连接&#xff08;source/sink) 的格式机制&#xff0c;更加清晰地分离了连接、格式和 Schema&#xff0c;同时支持了格式的自定义&#xff1b;受益于新的格式机制&#xff0c;我们大幅完善了文…

echarts中formatter修改鼠标悬浮事件信息操作、echarts地图块、散点区分触发点击事件 只触发散点问题详解

这里写目录标题1、实例2、案例详解1、实例 这次我拿echarts中 地图组合散点图的实例 &#xff01;&#xff01;&#xff01;实现效果&#xff1a;滑到散点显示不同于地图块的信息 及 formatter 提示窗自定义&#xff01;&#xff01;&#xff01; 这个显示项目名称为"文昌…

千锋教育+计算机四级网络-计算机网络学习-01

目录 课程链接 最早的广域网 计算机网络发展阶段 计算机网络的定义与要点 英文单词网络术语与解释 计算机网络分类 广域网技术 城域网 局域网 个人局域网 五种基本的网络拓扑结构​ 误码率 电路交换网特点 分组交换 交换方式 TCP/IP协议族 IP协议介绍 TCP协议介绍 …

OpenCV的solvePnP函数和Dlib估计头部姿势

一、姿势估计概述1、概述在许多应用中&#xff0c;我们需要知道头部是如何相对于相机倾斜的。例如&#xff0c;在虚拟现实应用程序中&#xff0c;可以使用头部的姿势来渲染场景的右视图。在驾驶员辅助系统中&#xff0c;在车辆中观察驾驶员面部的摄像头可以使用头部姿势估计来查…

React(coderwhy)- 06(RTK)

认识ReduxToolkit 认识Redux Toolkit ◼ Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。  在前面我们学习Redux的时候应该已经发现&#xff0c;redux的编写逻辑过于的繁琐和麻烦。  并且代码通常分拆在多个文件中&#xff08;虽然也可以放到一个文件管理&#xff0c;…

[RoarCTF 2019]Online Proxy(x-forwarded-for盲注)

这道题点开题目 然后题目显示不出网&#xff0c;一开始误认为是ssrf了&#xff0c;但是没有更多的信息了。 源码有一个ip会不会是修改X-Forwarded-For就可以了呢&#xff0c;抓包试一下&#xff0c; 发现有两个ip&#xff0c;一个当前一个是last上一个的意思把&#xff0c;…

数字图像处理实验——图像增强

一、实验目的与要求1.掌握图像空域点处理增强方法&#xff0c;包括图像求反&#xff0c;线性灰度变换&#xff0c;以及直方图均衡化方法&#xff1b;2.熟练掌握空域滤波增强方法&#xff0c;包括平滑滤波器及锐化滤波器。二、实验内容及步骤1.图像的求反、线性灰度变换、直方图…

【回答问题】ChatGPT上线了!比较流行的监督学习模型

监督学习模型是指在训练过程中&#xff0c;使用带有正确答案的标记数据来进行学习。常见的监督学习模型包括逻辑回归、决策树、支持向量机、朴素贝叶斯分类器、神经网络等。最近流行的监督学习模型还包括深度学习模型&#xff0c;如卷积神经网络和循环神经网络。 下面给出一些…

超级浏览器能帮来赞达(Lazada)老板什么忙?

近几年东南亚市场因人口红利及互联网的高度普及倍受关注&#xff0c;东南亚市场成了跨境市场的香饽饽&#xff0c;像来赞达&#xff08;Lazada&#xff09;、虾皮&#xff08;shopee&#xff09;等平台都是最近东南亚地区比较受欢迎的在线购物网站。根据第三方发布的调查报告显…

QGroundControl中使用QT语言家功能

QT语言家支持多种语言功能&#xff0c;在QGC中也很好的使用了该功能&#xff0c;下面对该功能是一些理解进行整理。首先在QGC使用语言家功能中分为qml文件和.c文件&#xff0c;两种略有不同。在.c文件中使用tr()将需要翻译的内容进行包裹在qml文件中使用qsTr()进行包裹下面以新…

【手把手一起学习】(一) Altium Designer 20 软件安装

Altium Designer 20 软件安装 1、解压安装压缩包 2、打开Setup文件夹 3、选中AltiumDesigner20Setup&#xff0c;选择“以管理员身份运行” 4、直接点击Next 5、选择“Chinese”&#xff0c;点击Next 6、直接点击Next 7、选择安装路径 8、直接点击Next 9、继续Next 10、软件安…

软件设计师通关攻略

软件设计师考什么&#xff1f;一、基础知识1.计算机科学基础知识2.计算机系统知识3.系统开发和运行知识4.面向对象基础知识5.信息安全知识6.标准化、信息化和知识产权基础知识7.计算机专业英语二、应用技术1.外部设计2.内部设计3.数据库应用分析与设计4.程序设计5.系统实施6.软…

【内网基于docker部署flink1.13.6 CDC、zeppelin0.10.0、flink-sql-cookbook-on-zeppelin】

内网基于docker部署flink1.13.6 CDC、zeppelin0.10.1、flink-sql-cookbook-on-zeppelin1、基础环境2、部署步骤2.1 docker安装2.2 拷贝docker镜像至内网2.2.1 外网机器下载所有需要镜像2.2.2 外网机器镜像检查2.2.3 外网机器镜像打包2.2.4 拷贝至内网2.2.5 内网加载镜像2.3 下载…

(剖面图全网唯一教程)如何利用EDEM制作剖面图 (自己琢磨出)

EDEM制作剖面图是一件非常重要的工作,本篇文章的方法是自己琢磨出来的,不一定完全精准,但是有效果,剖面图是可以制作出来的。方法在文末。

如何在图片上添加贴图?试试这几种方法

你平时在拍照的时候&#xff0c;有没有遇到过这样的情况&#xff1a;当你拍照后&#xff0c;发现背景不是很符合你的心意出现了一些你不想分享的物品&#xff0c;这个时候你会怎么办呢&#xff1f;一般情况下&#xff0c;有的小伙伴会选择使用马赛克来遮住这些物品&#xff0c;…

js如何引用同级元素

具体效果示例效果,可点击文末左下角阅读原文https://coder.itclan.cn/fontend/js/17-yinyong-tongji-elem/具体描述在网页中,同级(兄弟)元素,指的是拥有相同的直接父级元素的元素,并且往往指的是同类的元素,同类元素在实际开发中遇到的比较多比如:列表li,并列的按钮等,当需要做…

SpringBoot集成xxl-job分布式定时任务

一、xxl-job定时任务搭建下面这篇文章介绍了xxl-job平台搭建过程https://blog.csdn.net/xrq1995/article/details/126282290二、spring boot项目搭建1.创建项目2.pom文件引入<!-- xxl-job-core --><dependency><groupId>com.xuxueli</groupId><arti…