上一篇博文:绘制条形码的效果不是很好:利用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 编写代码如下:
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>
/// <param name="angelList">旋转角度集合</param>
/// <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>
/// <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>
/// <param name="pointList"></param>
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>
/// <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)计算两个点连线的夹角,这样就可以得到图片要旋转的近似角度值
好了,本文的内容到此结束。