复杂环境下的二维码识别
写在前面
好久没有更新文章了,最近做了一些有关二维码识别和图像处理相关的项目,这里给同学们做一个讲解和总结,话不多说上干货
二维码
了解二维码
QR 二维码,即快速响应码,源于 Quick Response 的缩写,是日本 Denso Wave 公司于 1994 年 9 月研制的一种矩阵二维码符号。我们这里主要用到的是 QR Code 和 Data Matrix (链接指向Wikipedia上的一些简单介绍,有兴趣的朋友可以了解一下这两中二维码是由那些部分组成的,如果你想做原生的二维码识别库,这些内容是必要的)
二维码识别库
我在项目过程中,使用了很多库,例如:
ZXing
BarCode
DataMatrix.Net
Dynamsoft Barcode Reader
这里比较大众的ZXing,他对QRCode码制的二维码非常友好,识别速度也很快,但是内部没有复杂的图像处理逻辑,所以只能识别符合规范的二维码,而我们既然要支持复杂情况下的二维码,只靠这个是不够的。这里我们需要了解的是这些库怎么用(国内程序员大部分就只是代码的搬运工,不要避讳这点,但要在使用中了解他是怎么做到的)。这些库的Demo在网上都有,我会在后续的文章中做一点汇总,这里就单纯的说说这几种识别库的优劣。
QR | DM | 复杂环境 | 识别速度 | 位置信息 | |
---|---|---|---|---|---|
ZXing | 支持 | 不支持 | 不支持 | 快 | 携带 |
BarCode | 支持 | 不支持 | 不支持 | 快 | 携带 |
DataMatrix.Net | 支持 | 支持 | 不支持 | 中等 | 不携带 |
Dynamsoft Barcode Reader | 支持 | 支持 | 支持 | 慢 | 携带 |
从这个表格中我们可以看到,Dynamsoft Barcode Reader是功能最强的,同时也牺牲了识别速度,这里就看你怎么取舍了。
图像处理
图像处理就用现在比较通用的Opencv来完成
Opcv使用
根据我的使用情况来看,Opencv对C++很友好,但是对C#好像就有点那啥了,我原本的想法是用C++写一些Opencv相关操作的库,然后C#调用C++的库,但是实际操作下来马上让我放弃了这个想法,第一关C#使用C++类库都让我折腾了半天。后来我找到了 Emgu CV 和 OpenCVSharp(被.Net包装后的Opencv),我这里直接就选择了后者,因为他更加接近于原生的Opencv,对于已经在C++用过Opencv的开发者来说,这无疑替我们剩下了很多重新学习的时间。
OpenCVSharp使用的一些坑
这里我会跟同学们说一下我在使用Opencv时遇到的一些问题和解决办法,不可能覆盖所有会发生的问题,但如果你遇到了一样的问题,可以参考我的解决方案。
-
为什么在Nuget中安装了 OpenCVSharp 还是不能正常使用呢?
第一个包中只是 OpenCV 中的一些算法的核心部分,所以我们在使用的时候还要安装OpenCvSharpX.runtime.win -
为什么我的图像资源已被读取,程序就崩溃了
这个是因为OpenCV本身对加载的图像大小是有限制的,这个限制就定义在 modules\imgcodecs\src\loadsave.cpp 这个文件中具体的代码如下:
static const size_t CV_IO_MAX_IMAGE_PARAMS = cv::utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PARAMS", 50);
static const size_t CV_IO_MAX_IMAGE_WIDTH = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_WIDTH", 1 << 20);
static const size_t CV_IO_MAX_IMAGE_HEIGHT = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_HEIGHT", 1 << 20);
static const size_t CV_IO_MAX_IMAGE_PIXELS = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PIXELS", 1 << 30);
- OpenCV下载安装和环境配置(主要是C++环境下使用)
VS2019+OpenCV4.1.0安装及整合详细步骤(版本通用)(这里借用一下大佬的文章,很详细。)
图像处理后的二维码识别
物理图像处理法
- 如果是打印出来的二维码,那很简单,避免光线的直射就行了。
- 如果是镭雕二维码(我的项目中主要解决的就是镭雕二维码的识别),一样的思路,环境复杂就意味着光线从不同的地方照射过来,然后以不同的角度反射出去,导致二维码显示不清晰,这里我们需要两个条件:所有光纤从同一个角度射出(固定光源+偏振片,不知道偏振光的可以去看一下初中的科学),相机只接收这个角度的光源(另一块偏振片),这样复杂的环境就变简单了。
通用处理方式
- 采集图像
使用相机采集图像,这个就不多说了,无非就是使用相机原厂提供的dll进行拉流,格式转换,保存。 - 分割图像
有时候分析一张有多个二维码的图像会占用大量时间,为了提高效率我们可以根据图像二维码分布规则来分割图像,创建线程分析单个二维码,这样可以牺牲一点资源消耗,提高效率和用户体验
/// <summary>
/// 分割图像
/// </summary>
/// <param name="bitmap">源图片</param>
/// <param name="rows">将图像分割为几行</param>
/// <param name="cols">将图像分割为几列</param>
/// <returns>分割后的图片组</returns>
public static List<Bitmap> CaptureImage(Bitmap bitmap, int rows, int cols)
{
try
{
List<Bitmap> bitmaps = new List<Bitmap>();
if (bitmap != null)
{
//! 新位图的宽高
int newW = bitmap.Width/ cols;
int newH = bitmap.Height / rows;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
//! 初始位置
int offsetY = i * newH;
int offsetX = j * newW;
//创建新图位图,作为作图区域
Bitmap TempBitmap = new Bitmap(newW, newH, PixelFormat.Format32bppRgb);
Graphics graphic = Graphics.FromImage(TempBitmap);
//截取原图相应区域写入作图区
graphic.DrawImage(bitmap, 0, 0, new Rectangle(offsetX, offsetY, newW, newH), GraphicsUnit.Pixel);
bitmaps.Add(TempBitmap);
//! 资源释放
graphic.Dispose();
}
}
}
return bitmaps;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
- 图像滤波
一般我们会使用 高斯滤波 ,它本质上,是一种数据平滑技术,让你的图像波形更加平滑。也很容易理解,就是每个像素点都会取自周围像素点的平均值。
Cv2.GaussianBlur(Src, Res, new OpenCvSharp.Size(3, 3), 0);
我这里使用了33的高斯核,也可以使用55的,要是奇数。
- 二值化
二值化是二维码识别中最重要的一步,要把图像转换成符合规范的黑白图片,以便二维码识别库来识别二维码。
最简单的二值化就是遍历图像,像素点大于阈值的设为MaxValue,小于阈值的设为MinValue。但是这种方式的阈值会随着周围环境的光线变化而变化。每到一个环境就需要重新配置阈值,而且如果一次要识别多个二维码则需要对每个二维码所在的小图像设置不同阈值。
复杂一点的就是最大类间方差法(大津阈值法(Otsu)),这种方法会根据图像特性来选择最佳的阈值,这样就避免了我们每换一个地方就要重新设置阈值的尴尬。但这种方法也只能应对光照比较均匀的情况。如果遇到一个二维码上一半强光,一半阴影,处理出来的图像就会差很多。
if (BinaryMode == "Binary")
{
//! 二值化(大于阈值全变成白)
Cv2.Threshold(TmpMat3, TmpMat4, ThresholdValue, 255, ThresholdTypes.Binary);
}
else if (BinaryMode == "BinaryInv")
{
//! 二值化(大于阈值全变成黑)
Cv2.Threshold(TmpMat3, TmpMat4, ThresholdValue, 255, ThresholdTypes.BinaryInv);
}
else if (BinaryMode == "Otsu")
{
Cv2.Threshold(TmpMat3, TmpMat4, 0, 255, ThresholdTypes.Otsu);
}
二值化模式 | 具体操作 |
---|---|
Binary | 大于阈值的像素点设为MaxValue |
BinaryInv | 大于阈值的像素点设为MinValue |
Otsu | 找到最佳阈值,大于阈值的像素点设为MaxValue |
《光照不均 QR 二维码图像的高效处理方法研究》
因为自己做的图像处理比较Low,识别率低。使用外部的二维码识别库虽然好用识别率也很高,但是奈何只能试用不能商用,也不是开源的。只能继续查找相关内容。这篇论文给我提供了一个新的思路,将一个二位码分块,然后进行Otsu二值化处理,这样能很好的避免光线不均匀带来的二值化效果差的情况。在这之上再加上直方图均衡化处理,尽可能地介绍光线对图像的影响,虽然扫描速度与专业的识别库还存在差距,但是识别率较之前大大提高了。这部分代码我会在后续的文章中输出。
写在结尾
好久没更新文章了,既有工作强度比较大的原因,也有自身懈怠的问题,争取后续将文章更新提上日程。天道酬勤,与君共勉。