猿创征文|OpenCV 如何提高条形码识别率

猿创征文|OpenCV 如何提高条形码识别率

今天介绍一个使用OpenCV提高条形码识别率的算法

最近,在研究机器视觉的课题,除了百度等一些收费的项目,免费的算法库经过我的筛选,感觉OpenCV还不错,条码识别用到的是ZXing插件,但在识别条码(最近只涉及到了条形码)的时候识别率比较低。无论怎么优化都收效甚微。查阅了一些知识文档,不是实现难度太大就是效果不好,在某天深夜看着条码发呆的时候突然一个简单的算法浮现在脑海里,测试后果然效果非常明显,下面分享给各位猿友。欢迎批评指正~~

平台及OpenCV库简介

在界面方面我还是喜欢.Net 平台,网上讲解OpenCV的例子多为Python,无妨,其实方法都是类似的,关于winform线程的问题不在本文讨论之列。

  1. C# winform程序
  2. OpenCV库,直接NuGget引入OpenCVSharp库即可
  3. ZXing库引入
  4. 其他小插件看各位看官心情添加 平台搭建还是比较简单的。

强烈建议:先学习一下OpenCV的课程

B站是个好地方,免费资源很多,讲的也很好,视觉识别是一个和一般开发不太一样的赛道,直接看代码还是很吃力的。然后下载OpenCV源码及示例进行辅助理解。工欲善其事必先利其器,磨刀不误砍柴工。这给后面的工作无疑是扫清了大部分的障碍。

步入正题:从图片读取到条码截取部分(非重点,但很重要)

  1. 获取含有条形码的图片
    我不想赘述如何进行图片读取,OpenCV也有类似的接口连接摄像头,也可以直接读取图片,So easy.

贴点儿代码吧,不然以为我偷的文章,需要的童鞋可以自己看下,否则可以略过。以下是直接连接本机摄像头,也可以连接网络摄像头,可以自己选择。


                    VideoCapture video = VideoCapture.FromCamera(0);

                    bool video_isOpened = false;

                    if (video.IsOpened())
                    {
                        video_isOpened = true;
                        led_Status.BeginInvoke(new Action<string>(text => { led_Status.Text = text; led_Status.ForeColor = Color.Lime; }),"Opened");
                    }
                    else
                    {
                        led_Status.BeginInvoke(new Action<string>(text => { led_Status.Text = text; led_Status.ForeColor = Color.Red; }),"Closed");

                    }

                    Mat pre_frame = null;
                    if(video_isOpened)
                    {
                        Mat frame = new Mat();
                        video.Read(frame);
                        }

原图如下:敏感部位马赛克处理了

猿创征文|OpenCV 如何提高条形码识别率
  1. 图片处理过程,通过灰度变换、各种滤镜、图片透视等操作,此处不是本文重点,但是非常重要!!!需要根据不同的图片进行个性化操作,如果各种操作看不懂,还得再自学一下OpenCV的课程。也欢迎留言讨论,我有时间也会回复各位。以下为参考代码,。 ① 截取图片中白色标签部分(此处也自己写了一个算法,关于通过四条不连续的直线,计算出四个四
    边形的顶点,这里就不讨论了,感兴趣的话可以留言)
    ② 透视变换
    ③ 获取条码部分

            Toushi();

            MemoryStream ms = new MemoryStream();
            picBox_Pre.Image.Save(ms, picBox_Pre.Image.RawFormat);
            Mat src = Mat.FromImageData(ms.ToArray(), ImreadModes.Unchanged);

            Mat dst = Mat.FromImageData(ms.ToArray(), ImreadModes.Grayscale);

            Mat src_gray = dst.Clone();

            var open_kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(30, 1));
            dst = dst.MorphologyEx(MorphTypes.Open, open_kernel);

            Cv2.Threshold(dst, dst, 100, 200, ThresholdTypes.Binary);
            Mat edges = dst.Canny(80, 255);

            var contours = edges.FindContoursAsMat(RetrievalModes.External, ContourApproximationModes.ApproxSimple);

            OpenCvSharp.Rect barcodeRect=new OpenCvSharp.Rect();
            OpenCvSharp.Rect barcodeStringRect = new OpenCvSharp.Rect();
            OpenCvSharp.Rect splitLineRect = new OpenCvSharp.Rect();

            bool barcodeFlag = false;
            bool barcodeStringFlag = false;
            bool splitLineFlag = false;

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

                OpenCvSharp.Rect rect = contours[i].BoundingRect();

                Console.WriteLine($"rect width:--{rect.Width}----,height:---{rect.Height}----,w/h={rect.Width / rect.Height}");

                if (rect.Width > 700 && rect.Height > 150 && rect.Width / rect.Height > 2)
                {

                    Console.WriteLine($"barcode width:{rect.Width},height:{rect.Height},w/h={rect.Width / rect.Height}");

                    int extentX = 6;
                    int extentY = 5;
                    rect = new OpenCvSharp.Rect(new OpenCvSharp.Point(Convert.ToDouble(rect.X - extentX), Convert.ToDouble(rect.Y - extentY)),
                                     new OpenCvSharp.Size(Convert.ToDouble(rect.Width + 2 * extentX), Convert.ToDouble(rect.Height + 2 * extentY)));
                    barcodeRect = rect;
                    Cv2.Rectangle(src, rect, Scalar.DarkGreen, 4);

                    Mat selectedROI = src_gray.SubMat(rect);
                    string showtext = GetStandardBarCodeText(selectedROI.Clone());
                    txb_BarCode.BeginInvoke(new Action<string>(m => txb_BarCode.Text = m), showtext);

                    barcodeFlag = true;
                }

                if (rect.Width > 500 && rect.Height > 30 && rect.Width / rect.Height > 10)
                {

                    Console.WriteLine($"barcodeString width:{rect.Width},height:{rect.Height},w/h={rect.Width / rect.Height}");

                    int extentX = 6;
                    int extentY = 2;
                    rect = new OpenCvSharp.Rect(new OpenCvSharp.Point(Convert.ToDouble(rect.X - extentX), Convert.ToDouble(rect.Y - extentY)),
                                     new OpenCvSharp.Size(Convert.ToDouble(rect.Width + 2 * extentX), Convert.ToDouble(rect.Height + 2 * extentY)));

                    barcodeStringRect = rect;
                    Cv2.Rectangle(src, rect, Scalar.DarkGreen, 4);

                    Mat barcodeStringROI = src_gray.SubMat(rect);

                    string showtext = ImageToText(ImageToBytes(Image.FromStream(barcodeStringROI.Clone().ToMemoryStream())),"eng");
                    txb_Result.BeginInvoke(new Action<string>(m => txb_Result.Text = $"箱号:{m}\r\n"), showtext);

                    barcodeStringFlag = true;
                }

private void Toushi()
{

            MemoryStream ms = new MemoryStream();
            picBox_Pre.Image.Save(ms, picBox_Pre.Image.RawFormat);
            Mat src = Mat.FromImageData(ms.ToArray(), ImreadModes.Unchanged);

            Mat mat = src.Clone();
            OutputArray dst = null;

            mat.CvtColor(ColorConversionCodes.BGR2GRAY);

            mat = mat.GaussianBlur(new OpenCvSharp.Size(9, 9),0);

            InputArray element = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(7, 7));

            Mat edges = new Mat();
            Cv2.Canny(mat, edges, 75, 200);

            var contours = Cv2.FindContoursAsMat(edges, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);

            Console.WriteLine(contours.Length.ToString()+"-------");

            Mat show = src.Clone();
            for (int i = 0; i < contours.Length - 1; i++)
            {
                if (contours[i].MinAreaRect().Size.Width < 100)
                    continue;

                var w_h = contours[i].MinAreaRect().Size.Width / contours[i].MinAreaRect().Size.Height;

                if (w_h > 0.8 && w_h < 1.2)
                {

                    var approx = contours[i].ApproxPolyDP(0.02 * Cv2.ArcLength(contours[i], true), true);
                    var points = approx.ConvexHullPoints();
                    List<OpenCvSharp.Point> pts = new List<OpenCvSharp.Point>();

                    pts = GetFourIntersections(points);
                    points = pts.ToArray();

                    Mat normalizedImage = GetWarpPerspectiveMat(show, points);

                    picBox_Pre.Image = Image.FromStream(normalizedImage.ToMemoryStream());
                    break;
                }
                }

先根据轮廓形状进行分区,再将条码部分截取出来单独分析。

猿创征文|OpenCV 如何提高条形码识别率
最终结果为下图所示:可见本图条码并不清楚,而且打印粗细也不规范,经过透视后略有变形。
猿创征文|OpenCV 如何提高条形码识别率

条码图片处理部分(本文重点)

经过上面的步骤我们可以得到一个不易识别的条码图片,本人经过测试,这样的图片识别率相当低,图片稍微不清楚或者角度稍微变化就无法读取。于是我的思路是(当然是在各种尝试之后得出的办法),既然图片本身就有缺陷,即使再怎么变换,原图都很难达到要求,经过分析,可以自行画一个规范的条码出来。
前提条件还要从条码的原理来讲,条码是一系列竖条组成,靠宽度和距离来表示信息。
我的条码只有两种宽度(如果需要多种宽度,稍微修正一下代码即可实现),那么主要就是距离了。
算法原理:1)首先得到条码的轮廓,将轮廓用点来表示
2)那么条码的位置取轮廓中x坐标最小的点(可能会有误差,但是正常情况下肯定可以大幅度提高准确性)
3)轮廓中计算y相等的所有x值的差,得到最大值为宽度K
4)得到左上角坐标(x,0),再知道宽度即可画出一条宽为K的线(其实是一个矩形)
5)最终得到下图中最下边的标准条码。

猿创征文|OpenCV 如何提高条形码识别率
这样的条形码,再用ZXing去进行识别,识别率非常高,基本可以达到99.9%。
但是如果原图太不规则,神仙也没办法解析,不在考虑范围之内。

以下是部分处理代码,由于代码尚未进行整理,比较乱,请见谅,又看不懂的地方欢迎留言。


        private string GetStandardBarCodeText(Mat barCodeROI)
        {

            barCodeROI=barCodeROI.Resize(new OpenCvSharp.Size(860, 190), 0, 0);

            int padding = 3;

            Mat barCodeROI_new = barCodeROI;

            Cv2.ImShow("roi_new", barCodeROI_new);

            barCodeROI_new = barCodeROI_new.GaussianBlur(new OpenCvSharp.Size(1, 3), 1);
            barCodeROI_new = barCodeROI_new.Threshold(80, 255, ThresholdTypes.Binary);

            var open_kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(1, 3));
            barCodeROI_new = barCodeROI_new.MorphologyEx(MorphTypes.Open, open_kernel);

            var edge = barCodeROI_new.Canny(80, 255);
            var contours = edge.FindContoursAsMat(RetrievalModes.External, ContourApproximationModes.ApproxNone);

            Mat result = new Mat(new OpenCvSharp.Size(barCodeROI_new.Width + 40, barCodeROI_new.Height + 25), MatType.CV_8UC3, Scalar.White);
            Mat result2 = result.Clone();
            result2.DrawContours(contours, -1, Scalar.Red, 1);
            Cv2.ImShow("contours", result2);

            int maxWidth = 0;
            int minX=0;
            int lastX = 0;
            int barCounter = 0;
            List<Mat<OpenCvSharp.Point>> list = contours.ToArray().OrderBy(x => x.BoundingRect().Left).ToList();
            for (int i = 0; i < list.Count; i++)
            {
                maxWidth = 0;
                var rect = list[i].BoundingRect();
                if (rect.Height / rect.Width <6)
                {

                    Console.WriteLine($"rect.Height / rect.Width={rect.Height / rect.Width}");
                    continue;

                }

                minX = list[i].ToArray().Min(c => c.X);
                if (minX - lastX < 5)
                {

                    Console.WriteLine($"minX={minX},lastX={lastX}, {minX - lastX}");
                    continue;
                }
                else
                {
                    lastX = minX;
                }

                int minY = list[i].ToArray().Min(p => p.Y);
                int maxY = list[i].ToArray().Max(p => p.Y);

                for (int y = minY; y  maxY; y++)
                {
                    int x1= list[i].ToArray().Where(p=>p.Y==y).Min(c => c.X);
                    int x2 = list[i].ToArray().Where(p => p.Y == y).Max(c => c.X);

                    maxWidth = Math.Max(maxWidth, x2 - x1 + 1);

                }

                int paddingTop = 10;
                int paddingBottom = 5;
                int paddingLeft = 10;

                Console.WriteLine($"width:----{maxWidth}");

                maxWidth = GetStandardWith(maxWidth);

                Console.WriteLine($"Index {i + 1} x:{minX},width:{maxWidth}");

                OpenCvSharp.Rect barRect = new OpenCvSharp.Rect(new OpenCvSharp.Point(minX + paddingLeft, paddingTop),
                    new OpenCvSharp.Size(maxWidth, result.Height - paddingBottom - paddingTop));

                result.Rectangle(barRect, Scalar.Red,-1);

                barCounter++;
            }
            Console.WriteLine($"result size:{result.Size()},barCounter:{barCounter}");
            Cv2.ImShow("barNew", result);

            string barCode = ZXingReadBarCode(result);

            return barCode;
        }

        private int GetStandardWith(int maxWidth)
        {

            if (maxWidth < 10)
            {
                maxWidth = 7;
            }
            else
            {
                maxWidth = 17;
            }

            return maxWidth;
        }

====================================

简码笔记,让你的代码更加简约精炼。

转载请注明出处。

Original: https://blog.csdn.net/happyxjbf/article/details/126699846
Author: 简码笔记
Title: 猿创征文|OpenCV 如何提高条形码识别率

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/701227/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球