利用ZXing.Net Bindings for EmguCV识别条形码及绘制条形码边框17(C#)
上一篇博文:绘制条形码的效果不是很好:利用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)计算两个点连线的夹角,这样就可以得到图片要旋转的近似角度值
好了,本文的内容到此结束。
相关文章:
利用ZXing.Net Bindings for EmguCV识别条形码及绘制条形码边框17(C#)
上一篇博文:绘制条形码的效果不是很好:利用Emgucv绘制条形码边框16(C#)-CSDN博客 测试环境: win11 64位操作系统 visual studio 2022 ZXing.Net.Bindings.EmguCV 0.16.4 测试步骤如下: 1 新建.net framework 4.8的控制台项目…...
IP代理如何增强网络安全性?
在当今的数字时代,网络安全已成为一个关键问题,而使用 IP 代理可以成为增强网络安全的有效方法。根据请求信息的安全性,IP 代理服务器可分为三类:高级匿名代理、普通匿名代理和透明代理。此外,根据使用的用途ÿ…...
NDP(Neighbor Discovery Protocol)简介
定义 邻居发现协议NDP(Neighbor Discovery Protocol)是IPv6协议体系中一个重要的基础协议。邻居发现协议替代了IPv4的ARP(Address Resolution Protocol)和ICMP路由设备发现(Router Discovery),…...
为何要隐藏源 IP 地址?
概述 在网络世界中,服务器的安全至关重要。一旦服务器遭受黑客攻击,采取正确的防御措施是防止进一步损害的关键。其中一项重要的策略就是隐藏服务器的真实 IP 地址。本文将探讨隐藏源 IP 地址的重要性,并提供一些实用的方法来实现这一目标。…...
目前最流行的前端构建工具,你知道几个?
现在的市面上有很多不同的前端构建工具,我们很难对它们一一进行关注。在本文中,我们将重点介绍最受欢迎的几种,并探讨开发人员喜欢或不喜欢它们的原因。 Webpack Webpack 是一个模块打包器,主要用于处理 Web 应用程序的资源的优化…...
C++函数模板温习总结
函数模板 // 1、typename 在这里是类型重定义(typedef),而不是宏替换(#define) //2、模板的非类型参数,属性为const , 不允许修改 //3、函数模板不允许部分特例化,类模板可以 //4、模板函数和非模板函数重载,优先调用…...
【网络】套接字(socket)编程——TCP版
接着上一篇文章:http://t.csdnimg.cn/GZDlI 在上一篇文章中,我们实现的是UDP协议的,今天我们就要来实现一下TCP版本的 接下来接下来实现一批基于 TCP 协议的网络程序,本节只介绍基于IPv4的socket网络编程 基于 TCP 的网络编程开…...
水凝胶生物打印是什么?如何指导Organoids培养?有啥好处?
大家好,我们来了解这篇《Hydrogel-in-hydrogel live bioprinting for guidance and control of organoids and organotypic cultures》发表在《Nature Communications》的一篇文章。三维水凝胶基器官样培养,如类器官和体外器官型培养,能够自我…...
从springBoot框架服务器上下载文件 自定义一个启动器
在springboot框架中下载服务器存储的图片: 1)springboot默认访问放行的目录只有static,在static目录下存放图片资源 2)编译后的static目录中有一个1.png 2.5)编写控制器: Controller //RequestMapping("/upload&q…...
某通电子文档安全管理系统 CDGAuthoriseTempletService1接口SQL注入漏洞复现 [附POC]
文章目录 某通电子文档安全管理系统 CDGAuthoriseTempletService1接口SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现0x06 修复建议某通电子文档安全管理系统 CDGAuthoriseTempletService1接口SQL注入漏…...
pythonselenium自动化测试实战项目(完整、全面)
前言 之前的文章说过, 要写一篇自动化实战的文章, 这段时间比较忙再加回家过11一直没有更新博客,今天整理一下实战项目的代码共大家学习。(注:项目是针对我们公司内部系统的测试,只能内部网络访问,外部网络…...
如何选择合适的虚拟机软件?对比Parallels Desktop 和VMware Fusion 使用虚拟机畅玩黑神话悟空
随着技术的发展,虚拟机软件将更加高效地管理和分配系统资源。虚拟机软件扮演着越来越重要的角色。无论是软件开发者需要测试不同操作系统环境下的应用,还是普通用户希望在一台机器上同时运行多个操作系统,虚拟机软件都是不可或缺的工具。那么…...
ESP32FreeRTOS开发笔记:2.定义、多任务与优先级调度
FreeRTOS 是一种实时操作系统(RTOS),专门用于嵌入式系统。它之所以被称为 "FreeRTOS",是因为它是一个免费和开源的 RTOS。下面我们具体讨论一下 FreeRTOS 与 RTOS 的区别,以及 "free" 的含义。 一、什么是 RTOS? RTOS,全称 Real-Time Operating Sy…...
【Python-办公自动化】1秒比较出2张表格之间的不同并标黄加粗
欢迎来到"花花 Show Python",一名热爱编程和分享知识的技术博主。在这里,我将与您一同探索Python的奥秘,分享编程技巧、项目实践和学习心得。无论您是编程新手还是资深开发者,都能在这里找到有价值的信息和灵感。 自我介…...
Linux下查看各进程的swap
cat /etc/re*se Red Hat Enterprise Linux Server release 6.8 (Santiago) 简单的可以通过top命令查看 top 后 按 f 进入选择列界面 按 p 就会输出swap信息(变为P) 回车返回看到SWAP信息了 再按 F 再按p 按swap排序 再回车后就是各进程按swap排序…...
最后一个单词的长度 简单字符串问题
给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1: 输入:s "Hello World" 输出:5 解…...
Autodesk Mudbox 2024:重塑创意边界的3D数字绘画与雕刻利器
在数字艺术与设计领域,Autodesk Mudbox 2024以其卓越的性能和直观的操作界面,再次刷新了3D数字绘画与雕刻软件的标准。作为Autodesk家族的一员,Mudbox不仅继承了其家族强大的技术基因,更在细节上精雕细琢,为艺术家和设…...
【python下用sqlite3, 多线程下报错,原因和解决 】
在python下用sqlite3, 多线程 在UPDATE 或者INSERT的时候, 会报错 sqlite3.OperationalError: cannot commit - no transaction is active 1. 原因 多线程写冲突 非原子写操作:如果多个线程同时执行非原子写操作,可能会导致数据覆盖或不一致。 2. 解…...
学习记录——day30 网络编程 端口号port 套接字socket TCP实现网络通信
目录 一、端口号 port 二、套接字 socket 1、原理 2、socket函数介绍 三、TCP实现网络通信 1、原理 2、TCP通信原理图 3、TCP相关函数 1)bind 绑定 2)listen 监听 3)accept 接收连接请求 4)recv 接收 5)sen…...
【DataKit系列】数据迁移-实例搭建步骤(二)
说明:此文档仅包含使用DataKit进行数据迁移时,搭建迁移任务相关教程,不包含一些必须的前置配置步骤,和环境要求等,请优先学习“【DataKit系列】数据迁移-使用说明(一)”文档。 数据迁移实例搭建…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...
VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...
ArcPy扩展模块的使用(3)
管理工程项目 arcpy.mp模块允许用户管理布局、地图、报表、文件夹连接、视图等工程项目。例如,可以更新、修复或替换图层数据源,修改图层的符号系统,甚至自动在线执行共享要托管在组织中的工程项。 以下代码展示了如何更新图层的数据源&…...
