利用ZXing.Net Bindings for EmguCV识别条形码及绘制条形码边框17(C#)

zxy2847225301 2024-08-16 09:07:06 阅读 77

上一篇博文:绘制条形码的效果不是很好:利用Emgucv绘制条形码边框16(C#)-CSDN博客

测试环境:

win11 64位操作系统

visual studio 2022

ZXing.Net.Bindings.EmguCV 0.16.4

测试步骤如下:

1  新建.net framework 4.8的控制台项目,项目名称为:BarCodeDemo,并把项目的目标架构修改为x64的,如下图:

2  通过nuget安装ZXing.Net.Bindings.EmguCV,版本选择0.16.4,如下图:

可以看到会自动把Emgu.CV 4.6.0.5131的版本也一起下载了,这时,还需要在github上下载运行需要的dll,下载链接:

https://github.com/emgucv/emgucv/releases/tag/4.6.0

把这个压缩包下载下来后解压,展开libs目录的runtimes目录

接着展开runtimes目录,如下图:

把win-x64目录的native目录下的文件全部拷贝到项目的运行目录Debug目录下

3  编写代码如下:

<code>using Emgu.CV;

using Emgu.CV.CvEnum;

using Emgu.CV.DepthAI;

using Emgu.CV.Ocl;

using Emgu.CV.Reg;

using Emgu.CV.Structure;

using Emgu.CV.Util;

using System;

using System.Collections.Generic;

using System.Diagnostics;

using System.Drawing;

using System.Linq;

using System.Security.Policy;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

using ZXing;

namespace BarCodeDemo

{

class Program

{

static void Main(string[] args)

{

ReadMultiBarCode5("test5.png");

Console.ReadLine();

}

static void ReadMultiBarCode5(string imageFileName)

{

Emgu.CV.Mat capturedFrame = Emgu.CV.CvInvoke.Imread(imageFileName);

List<Point[]> pointList= GetBarCodeRectPoint(capturedFrame);

if (pointList != null && pointList.Count > 0)

{

DrawRectPoint(capturedFrame, pointList);

List<int> list=GetRotateAngle(pointList);

if (list != null && list.Count > 0)

{

// 创建一个Stopwatch实例

Stopwatch stopwatch = new Stopwatch();

// 开始计时

stopwatch.Start();

List<string> barcodeList=GetBarCodeText(imageFileName, list);

foreach (var item in barcodeList)

{

Console.WriteLine("readCode:"+item);

}

// 停止计时

stopwatch.Stop();

// 输出耗时

Console.WriteLine("costTime: " + stopwatch.ElapsedMilliseconds + " ms");

}

}

//CvInvoke.Polylines(capturedFrame, contours, true, new MCvScalar(0, 255, 0), 5);

CvInvoke.Imshow("after_pic", capturedFrame);

CvInvoke.WaitKey(0);

}

/// <summary>

/// 获取条码内容

/// </summary>

/// <param name="imageFileName">图片文件名</param>code>

/// <param name="angelList">旋转角度集合</param>code>

/// <returns></returns>

private static List<string> GetBarCodeText(string imageFileName,List<int> angelList)

{

List<string> barCodeTextList = new List<string>();

// 加载图像

Bitmap bitmap = (Bitmap)System.Drawing.Image.FromFile(imageFileName);

object lockObject = new object();

Parallel.ForEach(angelList, angle => {

Bitmap bitMapCopy = null;

lock (lockObject)

{

bitMapCopy = (Bitmap)bitmap.Clone();

}

// 创建条码读取器

BarcodeReader reader = new BarcodeReader();

reader.Options.TryHarder = true;

reader.Options.PossibleFormats = new List<BarcodeFormat> { BarcodeFormat.CODE_39, BarcodeFormat.CODE_128 };

reader.Options.UseCode39ExtendedMode = true;

reader.Options.UseCode39RelaxedExtendedMode = true;

Bitmap rotatedBitmap = RotateImage(bitMapCopy, angle);

var result = reader.DecodeMultiple(rotatedBitmap);

if (result != null && result.Length > 0)

{

foreach (var item in result)

{

if (!barCodeTextList.Contains(item.Text))

{

barCodeTextList.Add(item.Text);

}

}

}

});

//foreach (var angle in angelList)

//{

// Bitmap rotatedBitmap = RotateImage(bitmap, angle);

// var result = reader.DecodeMultiple(rotatedBitmap);

// if (result != null && result.Length > 0)

// {

// foreach (var item in result)

// {

// if (!barCodeTextList.Contains(item.Text))

// {

// barCodeTextList.Add(item.Text);

// }

// }

// }

//}

return barCodeTextList;

}

private static Bitmap RotateImage(Bitmap bmp, float angle)

{

Bitmap rotatedImage = new Bitmap(bmp.Width, bmp.Height);

using (Graphics g = Graphics.FromImage(rotatedImage))

{

g.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);

g.RotateTransform(angle);

g.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);

g.DrawImage(bmp, new Point(0, 0));

}

return rotatedImage;

}

/// <summary>

/// 获取条形码的矩形信息

/// </summary>

/// <param name="capturedFrame"></param>code>

/// <returns></returns>

private static List<Point[]> GetBarCodeRectPoint(Emgu.CV.Mat capturedFrame)

{

Mat dstMat = new Mat();

//原图先转灰度图

CvInvoke.CvtColor(capturedFrame, dstMat, Emgu.CV.CvEnum.ColorConversion.Rgb2Gray);

CvInvoke.AdaptiveThreshold(dstMat, dstMat, 255, Emgu.CV.CvEnum.AdaptiveThresholdType.GaussianC, Emgu.CV.CvEnum.ThresholdType.BinaryInv, 7, 7);

Mat dstDilate = new Mat();

Mat dilateElement = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.Rectangle, new Size(20, 20), new Point(-1, -1));

//膨胀,核为20*20,搞大点

CvInvoke.Dilate(dstMat, dstDilate, dilateElement, new Point(-1, -1), 1, Emgu.CV.CvEnum.BorderType.Default, new MCvScalar(255, 0, 0));

//腐蚀

Mat erodeElement = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.Rectangle, new Size(10, 10), new Point(-1, -1));

Emgu.CV.CvInvoke.Erode(dstDilate, dstDilate, erodeElement, new Point(-1, -1), 10, BorderType.Default, new MCvScalar(255, 0, 0));

Mat dilateAfterElement = CvInvoke.GetStructuringElement(Emgu.CV.CvEnum.ElementShape.Rectangle, new Size(3, 3), new Point(-1, -1));

CvInvoke.Dilate(dstDilate, dstDilate, dilateAfterElement, new Point(-1, -1), 10, Emgu.CV.CvEnum.BorderType.Default, new MCvScalar(255, 0, 0));

VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

VectorOfRect hierarchy = new VectorOfRect();

//查找轮廓

CvInvoke.FindContours(dstDilate, contours, hierarchy, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);

if (contours.Size > 0)

{

List<Point[]> pointList = new List<Point[]>();

for (int i = 0; i < contours.Size; i++)

{

var contour = contours[i];

if (contour != null && contour.Size > 0)

{

//获取最小的矩形框

var rect = CvInvoke.MinAreaRect(contour);

//获取矩形框的点集合

PointF[] vertixes = rect.GetVertices();

if (vertixes != null && vertixes.Length > 0)

{

Point[] pointArray = new Point[vertixes.Length];

for (int j = 0; j < vertixes.Length; j++)

{

pointArray[j] = new Point((int)vertixes[j].X, (int)vertixes[j].Y);

}

pointList.Add(pointArray);

}

}

}

return pointList;

}

return null;

}

/// <summary>

/// 绘制矩形

/// </summary>

/// <param name="capturedFrame"></param>code>

/// <param name="pointList"></param>code>

private static void DrawRectPoint(Emgu.CV.Mat capturedFrame, List<Point[]> pointList)

{

foreach (var item in pointList)

{

CvInvoke.Polylines(capturedFrame, item, true, new MCvScalar(0, 255, 0), 10);

}

}

/// <summary>

/// 获取要旋转的角度

/// </summary>

/// <param name="pointList"></param>code>

/// <returns></returns>

private static List<int> GetRotateAngle(List<Point[]> pointList)

{

List<int> list = new List<int>();

list.Add(180);

foreach (var array in pointList)

{

Point minXPoint = array[0];

Point maxXPoint = array[0];

Point minYPoint = array[0];

Point maxYPoint = array[0];

int minX = array[0].X;

int minY = array[0].Y;

int maxX= array[0].X;

int maxY= array[0].Y;

foreach (var item in array)

{

int X = item.X;

int Y = item.Y;

if (X < minX)

{

minX= X;

minXPoint.X = X;

minXPoint.Y = Y;

}

if (Y < minY) {

minY = Y;

minYPoint.X = X;

minYPoint.Y = Y;

}

if (X > maxX)

{

maxX = X;

maxXPoint.X = X;

maxXPoint.Y = Y;

}

if (Y > maxY)

{

maxY= Y;

maxYPoint.X = X;

maxYPoint.Y = Y;

}

}

double yIndex = (double)(maxYPoint.Y - minXPoint.Y);

double xIndex = (double)(maxYPoint.X - minXPoint.X);

int angleOfLine = (int)(Math.Atan2(yIndex,xIndex) * 180 / Math.PI);

if (!list.Contains(angleOfLine))

{

list.Add(angleOfLine);

list.Add(Math.Abs(180-angleOfLine));

}

}

return list;

}

}

}

测试用到的test5.png图片如下:

程序运行的结果如下:

4  程序核心算法分析

4.1  GetBarCodeRectPoint方法是获取条形码边框的点的集合,其中用到的图像处理的相关知识:

4.1.1  先用CvInvoke.CvtColor把图像转换为灰度图

4.1.2  再用CvInvoke.AdaptiveThreshold获取到灰度图的轮廓

4.1.3  接着使用CvInvoke.Dilate把灰度图的轮廓进行膨胀处理,由于是条形码,所以卷积核用得比较大,用了20*20的卷积核,目的是为了把条形码中的竖线给整合成一个整体

4.1.4  接着使用Emgu.CV.CvInvoke.Erode对膨胀后的图片进行腐蚀处理,卷积核也搞得比较大,用了10*10的卷积核,但比前面的膨胀用的核小,可以看到只剩下4个条码的面积部分了,如下图:

4.1.5  接着再进行膨胀处理,使得圆滑一点:

4.1.5  后面的就是获取轮廓的点集合,存在List<Point[]>集合中,List集合中有4个Point[]数组

4.1.6  想要有好一点的识别效果,可以调整第一膨胀的卷积核的大小,图片的清晰度等

4.2  使用DrawRectPoint绘制条形码的边框

4.2  GetRotateAngle是为了获取到识别条码的旋转角,为何要获取旋转角度呢?是因为ZXing.Net这个库识别条码时,如果条码有倾斜的话(如下图这种图片),直接用ZXing.Net识别是无法识别的

如果想要识别出来,那就得旋转图片,这就得要360度旋转图片,每次旋转10读,那就得旋转36次,速度就很慢了

目前能想到的算法是:根据获取到条码轮廓最左边的那个点和最下面的那个点,然后计算这两个点连线,计算夹角,得到对应要旋转的角度A,另外的要旋转的角度为180-A

然后通过int angleOfLine = (int)(Math.Atan2(yIndex,xIndex) * 180 / Math.PI)计算两个点连线的夹角,这样就可以得到图片要旋转的近似角度值

好了,本文的内容到此结束。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。