配置环境: vs2019 , .Net Framework 4.8 , opencvsharp4
给大家介绍一下实现的主要思路吧。
使用的主要方法 Cv2.InRange() ,该方法的作用就是得到输入图像在指定颜色范围内的部分。通过该函数的输出图像就能判断输入的健康码是什么颜色了。
首先,我们需要判断一张图像内是否有二维码。
直接采用 opencvsharp 里面的QRCodeDetector ,可以根据该方法的返回值来判断是否含有二维码,true的话表示有二维码,同时输出的 points 为二维码四个顶点的坐标,false表示没有,输出的坐标都为0.
public static bool DetectQRCode(Mat src, out OpenCvSharp.Point2f[] points)
{
QRCodeDetector qrCode = new QRCodeDetector();
return qrCode.Detect(src, out points);
}
对于大部分的健康码而言,并不是仅仅只有一个二维码,周围还有很多的干扰项,我们在操作时只需要二维码部分。这是就需要用到掩码了。
首先定义一个输入图像大小相同的单通道图像,再根据提取到的二维码的顶点坐标,填充其坐标内部。
Mat mask = new Mat(src.Size(), MatType.CV_8UC1);
List<List<OpenCvSharp.Point>> pps = new List<List<OpenCvSharp.Point>>()
{ new List<OpenCvSharp.Point>() { points[0], points[1], points[2], points[3] } };
// 建立一个空的图像 只填充二维码部分 其余部分都为0
Cv2.FillPoly(mask, pps, new Scalar(255, 255, 255));
掩码定义好后就可以从原图中提取出二维码了;最好将原图转到HSV空间,这样效果比较好
图像自身与自身做与运算得到的还是本身,加上掩码之后就能拿到任意ROI了。
// 转成HSV空间
Mat hsvImg = new Mat();
Cv2.CvtColor(src, hsvImg, ColorConversionCodes.BGR2HSV);
// 提取ROI
Cv2.BitwiseAnd(hsvImg, hsvImg, hsvImg, mask);
得到HSV空间的图像后就能使用 Cv2.InRange() 得到你想要的颜色部分了。
颜色范围可以根据网上的HSV表设置
我根据这个设置,发现跟我的效果差的比较远,所以我就自己设置了几个颜色。
但也有一个问题就是当输入图像是黄色时,用绿色也能捕捉到。其余没什么问题
Scalar greenScMin = new Scalar(25, 52, 72); //绿
Scalar greenScMax = new Scalar(102, 255, 255);
Scalar yellowScMin = new Scalar(11, 43, 46); // 黄
Scalar yellowScMax = new Scalar(34, 255, 255);
Scalar redScMin = new Scalar(0, 43, 46); //红
Scalar redScMax = new Scalar(10, 255, 255);
Scalar blackScMin = new Scalar(0, 0, 0); // 黑
Scalar blavkScMax = new Scalar(180, 255, 46);
下面是 Cv2.InRange() 后几组不同的结果,健康码或者二维码都是网上找的照片或者自己生成的二维码。
当输入为红码时, 想要的颜色范围为红色。输出就红色的部分为白色255,其余都是黑色0.
当输入为红码时,想要的颜色范围为绿色时,输出如下,发现没有白色部分,都是0
黄码 黄色
绿码 绿色
绿码 黄色
发现效果都还可以。我这样设置颜色的话,经过我多次实验,发现,绿色只能用绿色捕捉到,而黄色能用绿色和黄色捕捉到。我们只需要在绿色捕捉到的情况下,判断黄色有没有捕捉到就行了,不影响我们的最终结果。
一般而言,我们可以根据最后的输出图像以及输入的颜色范围,判断有没有白色部分输出即255的部分就能判断是什么颜色了。但是大部分的健康码中心都会有一个logo,这个logo的颜色可能会导致输出图像有些问题。logo只是占小部分,所以我们就计数,用不同的颜色得到不同的输出图像,分别计算每张图像像素在二维码范围内像素值等于255的点的数量,最多的就认为是整个二维码的颜色。
int ScalarCode(Mat mm, Scalar min, Scalar max, Mat maskMat)
{
Rect rect = Cv2.BoundingRect(pts);
int k = 0;
Mat dst = new Mat();
Cv2.InRange(mm, min, max, dst);
for (int i = 0; i < mm.Rows; i++)
{
for (int j = 0; j < mm.Cols; j++)
{
// maskMat填充的部分就是二维码的部分 其他位置就不需要考虑了
if (maskMat.At<byte>(i, j) != 255) continue;
if (dst.At<byte>(i, j) == (byte)255)
{
k++;
}
}
}
return k;
}