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

news2024/11/15 2:01:20

上一篇博文:绘制条形码的效果不是很好:利用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)计算两个点连线的夹角,这样就可以得到图片要旋转的近似角度值

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2038065.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

正确利用AI工具,你的facebook广告效果将翻倍

如今投放facebook广告&#xff0c;你面临的对手已经不再是广告投手&#xff0c;而是AI&#xff0c;如果你的广告效果一直无法提升&#xff0c;不妨试着借助一下AI的力量&#xff0c;今天这篇文章就教你怎样才能让AI发挥它的最大价值&#xff0c;帮助我们的facebook广告效果提升…

STM32驱动SG90舵机完成控制

一、前言 SG90舵机的工作原理主要是基于PWM&#xff08;脉冲宽度调制&#xff09;信号来控制。 SG90舵机内部有一个基准电压&#xff0c;微处理器产生的PWM信号通过信号线进入舵机&#xff0c;产生直流偏置电压&#xff0c;与舵机内部的基准电压做比较获得电压差输出。电压差的…

精品在线试题库系统

TOC springboot108精品在线试题库系统 绪论** 1.1 研究背景 现在大家正处于互联网加的时代&#xff0c;这个时代它就是一个信息内容无比丰富&#xff0c;信息处理与管理变得越加高效的网络化的时代&#xff0c;这个时代让大家的生活不仅变得更加地便利化&#xff0c;也让时…

Carry你飞驰:VELO Prevail TT坐垫,不是超人,也能飞!

确认过黑丝腿&#xff0c;是很爱骑的人&#xff01;以前脚踩二八大杠&#xff0c;就能赚足出街回头率&#xff0c;现在的打工牛马是坚持早c晚c&#xff0c;晨骑夜骑解压续命。夏日的阳光炙烤着大地&#xff0c;空气中弥漫着滚滚热浪&#xff0c;对于每一位热爱骑行的骑士来说&a…

WindowsAPI 查阅笔记:网络通信

客户端&#xff1a; 记得在编译的时候加上这个 -lwsock32 -lws2_32。 不然会报错 undefined reference to __imp_WSAStartup‘。 注意&#xff1a;如果端口在此之前被占了&#xff0c;则不会发生预期的结果 服务端&#xff0c;得到连接后创建线程&#xff0c;执行处理函数。…

你真的了解电子标签的潜力吗?3秒快刷颠覆你的想象

随着ESL电子标签在零售领域的大范围应用&#xff0c;其方便快捷更改显示内容的功能也逐渐拓展到仓储显示领域。但是仓储作业过程中货品出入库频繁&#xff0c;常规电子标签在实际使用过程中存在刷新速度偏长&#xff0c;无法充分满足仓储出入库数据更新的需求。因此&#xff0c…

阿里云智能大数据演进

本文根据7月24日飞天发布时刻产品发布会、7月5日DataFunCon2024北京站&#xff1a;大数据大模型.双核时代实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;徐晟 阿里云研究员/计算平台产品负责人 主要内容&#xff1a; Overview - 阿里云大数据 AI 产品…

秋招突击——面经整理——有塔游戏提前批

文章目录 引言正文一面说一下堆排序 二面有了解过游戏后端应该是干什么的吗&#xff1f;博客是从什么时候开始写的&#xff1f;平常在哪里做题&#xff1f;做了多少题&#xff1f;给你二维矩阵&#xff0c;零代表可以走&#xff0c;一代表不可以走&#xff0c;从起点到终点&…

Java:文件IO

JavaEE16 一、文件系统操作 在java标准库中&#xff0c;实现了File类&#xff0c;其中提供了文件操作的方法&#xff01; 1、构造方法&#xff1a; 方法名说明 File(File parent , String child) 根据父目录孩子文件路径&#xff0c;创建一个新的File 实例File( String path…

dockers 阿里云镜像失效后如何配置,可视化操作

Windows 环境 Client:Version: 24.0.6Context: defaultDebug Mode: false方法一 docker-desktop 配置方式 {"builder": {"gc": {"defaultKeepStorage": "20GB","enabled": true}},"experimental": false,…

【echarts】甘特图

const milestones [{ progress: 100, milestoneName: 阶段一, startDate: 2020-12-23, endDate: 2021-01-30 },{ progress: 100, milestoneName: 阶段二, startDate: 2021-01-15, endDate: 2021-03-15 },{ progress: 100, milestoneName: 阶段三, startDate: 2021-03-10, endD…

ubuntu 24.04执行apt-get update报错处理

文章目录 一、apt-get update报错内容二、解决启动失败的服务推荐阅读 今天在做Ubuntu 24.04更新时&#xff0c;突然跳出两个报错&#xff0c;这在之前还从未遇到过&#xff0c;处理过程记录和分享一下&#xff1a; 一、apt-get update报错内容 报错截图参考如下&#xff1a; …

现货黄金美盘开盘时间是什么呢

现货黄金的开盘时间会因为地区和平台而有所差异&#xff0c;一般来说&#xff0c;香港的平台在北京时间周一早间7点左右会开盘&#xff0c;并一直持续周六凌晨才收盘&#xff0c;周六和周日是市场的休市时间&#xff0c;期间交易平台一般会关闭&#xff0c;无法进行交易&#x…

一文读懂什么是进销存!进销存有何价值作用?

企业在运营过程中&#xff0c;常常会遇到诸如库存不准确、采购计划混乱、销售数据跟踪困难等问题&#xff0c;这些问题不仅影响了企业的日常运营&#xff0c;还可能导致客户满意度下降、利润受损。而一个合适的进销存系统&#xff0c;就像是一把钥匙&#xff0c;可以帮助企业打…

MySQL基础练习题34-游戏玩法分析4

目录 题目 准备数据 分析数据 总结 题目 报告在首次登录的第二天再次登录的玩家的 比率&#xff0c;四舍五入到小数点后两位。换句话说&#xff0c;你需要计算从首次登录日期开始至少连续两天登录的玩家的数量&#xff0c;然后除以玩家总数。 准备数据 ## 创建库 create…

为什么要学医疗器械维修?

在当今高速发展的医疗领域&#xff0c;医疗器械维修已成为一个极具吸引力和潜力的职业选择。那么&#xff0c;为什么要学习医疗器械维修呢&#xff1f; 一、稳定且持续增长的需求 随着医疗技术的不断进步&#xff0c;医疗器械的种类和数量日益增多。从常见的 X 光机、CT 扫描仪…

新专利:温室土壤温湿度预测模型构建方法和程序产品

&#xff08; 于景鑫 国家农业信息化工程技术研究中心&#xff09;在现代设施农业生产中&#xff0c;温室微环境的精准调控是提高作物产量和品质的关键。然而&#xff0c;由于温室内外环境因素的复杂多变&#xff0c;尤其是土壤温湿度的非线性、非平稳特性&#xff0c;传统的预…

【vue3|第23期】Vite + Vue3: 深入理解public和assets文件夹的作用与使用

日期&#xff1a;2024年8月14日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

如果让你消息队列,该如何设计?说一下你的思路

在当今的分布式系统中&#xff0c;消息队列是一个不可或缺的组件&#xff0c;它在系统解耦、流量削峰、异步处理等方面发挥着重要作用。 如果我要设计一个消息队列&#xff0c;我会从以下几个关键点出发呢&#xff1f; 让我们来探讨一下如何设计一个消息队列的架构。 实现内存…

【MySQL】数据库初识

文章目录 前言一、MySQL的基本结构二、MySQL的组成三、数据库的简单使用数据库操作数据类型认知数值类型字符串类型日期类型 数据库表操作 总结 前言 数据库是一类软件&#xff0c;有MySQL、Oracle、SQL Server、Redis等作为代表&#xff0c;通过数据库能够对数据进行管理和组…