当前位置: 首页 > 科技观察

AOE工程实践银行卡OCR图像处理

时间:2023-03-16 02:17:51 科技观察

最近开发了一个银行卡OCR项目。需求是用手机对银行卡拍照后,通过推理可以识别出卡上的卡号。在工程开发过程中,我们发现手机拍摄的图像不能满足模型的输入要求。以Android为例,从摄像头获取的预览图是旋转90度的NV21格式的图片,而我们模型需要的输入只需要卡片区域的图片,需要转成固定大小的BGR格式。因此,在图像输入到模型之前,我们需要对采集到的图像进行图像处理,如下图所示:在开发过程中,我们学习了YUV图像格式和libyuv,积累了一些经验。下面就银行卡OCR项目涉及到的一些基础知识说一说:什么是YUV格式如何裁剪YUV图像如何旋转YUV图像跨图像处理如何缩放格式转换libyuv的使用要对YUV格式的抓拍图像进行处理,首先我们需要了解什么是YUV格式。什么是YUV格式YUV是一种颜色编码方式,YUV分为三个部分:“Y”代表亮度(Luminance或Luma),即灰度值;“U”和“V”代表色度(Chrominance或Chroma)。主流的采样方式有3种:YUV4:4:4YUV4:2:2YUV4:2:0。这部分专业知识网上有详细的解释。我们简单理解一下,RGB和YUV都是用三个值来描述一个像素点,但这三个值的含义是不一样的。通过一个固定的公式,我们可以将RGB和YUV相互转换。项目中常见的I420、NV21、NV12都属于YUV420,每四个Y共用一组UV分量。YUV420主要包括两种格式,YUV420SP和YUV420P。★YUV420SP,Y分量在前,UV分量交替排列,例如:NV12:YYYYYYYYUVUV和★NV21:YYYYYYYYVUVU(我们上面在Android上采集的图片就是这种格式)。YUV420P,先排列U(或V)分量,再排列V(或U)分量。例如:I420:YYYYYYYYUUVV和YV12:YYYYYYYYVVUU。了解了YUV的图像格式后,我们可以尝试对图像进行裁剪和旋转。我们的思路是先裁剪出图像上银行卡的区域,然后进行旋转。如何裁剪YUV图像YUV420SP和YUV420P的裁剪过程类似,以YUV420SP为例,我们要裁剪图片中的这个区域:在图片上看起来很明显,找到裁剪区域对应的Y分量和UV分量即可,逐行复制到目标空间。我们再看一张图,可以用上面的方法裁剪图片中的这个区域吗?答案是否定的,如果按照上面的方法,你会发现你保存的图片颜色基本不对,甚至可能出现内存错误。原因很简单。如果仔细观察,当ClipLeft或ClipTop为奇数时,会导致复制时UV分量错乱。如果错误的图像数据输入到模型中,我们肯定得不到我们期望的结果。所以我们在裁剪的时候,需要避免奇数的场景,否则会遇到意想不到的结果。如何旋转YUV图像将裁剪后的图像顺时针旋转90度以上。与裁剪相比,转换稍微复杂一些。基本方法相同,将对应的Y分量和UV分量复制到目标空间。在了解了cropping和rotation的方法后,我们发现在学习的过程中不可避免地遇到了Stride这个词。它在图像中的作用是什么?StrideStride在图像处理中是一个很重要的概念。Stride是指每行像素在内存中所占的空间。它是大于或等于图像宽度的内存对齐长度。如下图所示:回过头来看我们上面提到的裁剪和旋转,有什么问题吗?以Android上的YV12为例,GoogleDoc是这样描述的:YV12isa4:2:0YCrCbplanarformatcomprisedofaWxHYplanefollowedby(W/2)x(H/2)CrandCbplanes.Thisformatassumesanevenwidthanevenheightahorizo??ntalstridemultipleof16pixelsaverticalstrideequaltotheheighty_size=stride*heightc_stride=ALIGN(stride/2,16)c_size=c_stride*height/2size=y_size+c_b+yc_offset=y_size=在不同的平台和设备上,需要根据文档和stride计算。比如计算Buffer的大小,很多文章都是简单的“*3/2”。仔细想想,这其实是有问题的。不考虑stride会有什么后果?如果“运气”足够好,一切看起来都很正常。“运气”不够好,你会发现很多奇怪的问题,比如花屏、绿条纹、内存错误等等。这和我们日常工作中遇到的很多奇怪的问题是一样的。其实,背后有着深层次的原因。裁剪旋转后,我们只需要将图片缩放到模型需要的尺寸,并转换成模型需要的BGR格式即可。如何进行缩放和格式转换以缩放为例,有邻近插值法、线性插值法、三次插值法和Ranchos插值法等算法。YUV和RGB之间的转换公式有很多,比如量化和非量化。这些涉及到专业知识,需要大量的时间去学习和理解。我们必须自己实现这么多转换吗?很多优秀的开源项目已经为我们提供了完善的API,比如OpenCV、libyuv等,我们要做的就是了解基本原理,站在别人的肩膀上,心中有数,这样即使我们遇到问题,我们可以快速定位并解决。经过考察比较,我们选择libyuv作为图像处理库。libyuv是谷歌的开源库,实现了各种YUV和RGB之间的转换、旋转和缩放。跨平台,可在Windows、Linux、Mac、Android等操作系统、x86、x64和arm架构上编译运行,支持SSE、AVX、NEON等SIMD指令加速。libyuv的使用引入libyuv后,我们只需要调用libyuv提供的相关API即可。在使用银行卡OCR项目的过程中,我们主要遇到了两个问题:1.在Android开发初期,我们发现识别率和我们的预期有一定的差距。我们怀疑模型的输入数据有问题。经过排查,我们发现在使用libyuv的时候,并没有注意到它是littleendian。比如这个方法:intBGRAToARGB(...),BGRAlittleendian,在内存中的顺序其实就是ARGB。所以在使用的时候需要搞清楚你的??数据在内存中是什么顺序。修改这个问题后,识别率达到了我们的预期。2、在大部分机型上运行正常,但在部分机型上出现Native层内存异常。经过多次定位,最终发现是stride和buffersize的计算错误导致的。通过银行卡OCR项目,我们积累了相关经验。另外,由于libyuv是用C/C++实现的,使用起来不是那么方便。为了提高开发效率,我们提取了一个Vision组件,为libyuv封装了一层JNI接口,包括一些基本的转换和一些sample,使用起来更加方便。作为AOESDK中的图像处理组件,目前还在开发和完善中。