參考博客:https://www.cnblogs.com/dengxiaojun/p/5278679.html,但是他的demo下載太貴了
可以下載這個https://download.csdn.net/download/dsq235612/10830805?utm_source=bbsseo,其實代碼都差不多,目前只能識別簡單的結構的圖片
先添加引用,在nuget中添加OpenCVSharp類庫和識別條碼類庫zxing
封裝OpenCVSharp的調用代碼:
public class OpencvHelper { /// <summary> /// 灰度圖 /// </summary> /// <param name="srcImage">未處理的mat容器</param> /// <param name="grayImage">灰度圖mat容器</param> public static void CvGrayImage(Mat srcImage, Mat grayImage) { if (srcImage.Channels() == 3) { Cv2.CvtColor(srcImage, grayImage, ColorConversionCodes.BGR2GRAY); } else { grayImage = srcImage.Clone(); } //Imshow("灰度圖", grayImage); } /// <summary> /// 圖像的梯度幅值 /// </summary> /// <param name="grayImage"></param> public static void CvConvertScaleAbs(Mat grayImage, Mat gradientImage) { //建立圖像的梯度幅值 Mat gradientXImage = new Mat(); Mat gradientYImage = new Mat(); Cv2.Sobel(grayImage, gradientXImage, MatType.CV_32F, xorder: 1, yorder: 0, ksize: -1); Cv2.Sobel(grayImage, gradientYImage, MatType.CV_32F, xorder: 0, yorder: 1, ksize: -1); //Cv2.Scharr(grayImage, gradientXImage, MatType.CV_32F, 1, 0);//CV_16S CV_32F //Cv2.Scharr(grayImage, gradientYImage, MatType.CV_32F, 0, 1); //因為我們需要的條形碼在需要X方向水平,所以更多的關注X方向的梯度幅值,而省略掉Y方向的梯度幅值 Cv2.Subtract(gradientXImage, gradientYImage, gradientImage); //歸一化為八位圖像 Cv2.ConvertScaleAbs(gradientImage, gradientImage); //看看得到的梯度圖像是什么樣子 //Imshow("圖像的梯度幅值", gradientImage); } /// <summary> /// 二值化圖像 /// </summary> public static void BlurImage(Mat gradientImage, Mat blurImage, Mat thresholdImage) { //對圖片進行相應的模糊化,使一些噪點消除 //new OpenCvSharp.Size(12, 12); (9,9) Cv2.Blur(gradientImage, blurImage, new OpenCvSharp.Size(6, 6)); //Cv2.GaussianBlur(gradientImage, blurImage, new OpenCvSharp.Size(7, 7), 0);//Size必須是奇數 //模糊化以后進行閾值化,得到到對應的黑白二值化圖像,二值化的閾值可以根據實際情況調整 Cv2.Threshold(blurImage, thresholdImage, 210, 255, ThresholdTypes.Binary); //看看二值化圖像 //Imshow("二值化圖像", thresholdImage); } /// <summary> /// 閉運算 /// </summary> public static void MorphImage(Mat thresholdImage, Mat morphImage) { //二值化以后的圖像,條形碼之間的黑白沒有連接起來,就要進行形態學運算,消除縫隙,相當於小型的黑洞,選擇閉運算 //因為是長條之間的縫隙,所以需要選擇寬度大於長度 Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(21, 7)); Cv2.MorphologyEx(thresholdImage, morphImage, MorphTypes.Close, kernel); //看看形態學操作以后的圖像 //Imshow("閉運算", morphImage); } /// <summary> /// 膨脹腐蝕 /// </summary> public static void DilationErosionImage(Mat morphImage) { //現在要讓條形碼區域連接在一起,所以選擇膨脹腐蝕,而且為了保持圖形大小基本不變,應該使用相同次數的膨脹腐蝕 //先腐蝕,讓其他區域的亮的地方變少最好是消除,然后膨脹回來,消除干擾,迭代次數根據實際情況選擇 OpenCvSharp.Size size = new OpenCvSharp.Size(3, 3); OpenCvSharp.Point point = new OpenCvSharp.Point(-1, -1); Cv2.Erode(morphImage, morphImage, Cv2.GetStructuringElement(MorphShapes.Rect, size), point, 4); Cv2.Dilate(morphImage, morphImage, Cv2.GetStructuringElement(MorphShapes.Rect, size), point, 4); //看看形態學操作以后的圖像 //Imshow("膨脹腐蝕", morphImage); } /// <summary> /// 顯示處理后的圖片 /// </summary> /// <param name="name">處理過程名稱</param> /// <param name="srcImage">圖片盒子</param> public static void Imshow(string name, Mat srcImage) { using (var window = new Window(name, image: srcImage, flags: WindowMode.AutoSize)) { Cv2.WaitKey(0); } //Cv2.ImShow(name, srcImage); //Cv2.WaitKey(0); } /// <summary> /// 旋轉圖片 /// </summary> public static void RotateImage(Mat src, Mat dst, double angle, double scale) { var imageCenter = new Point2f(src.Cols / 2f, src.Rows / 2f); var rotationMat = Cv2.GetRotationMatrix2D(imageCenter, angle, scale); Cv2.WarpAffine(src, dst, rotationMat, src.Size()); } }
調用封裝的OpenCVSharp類的方法
/// <summary> /// 讀取圖片 /// </summary> private void DiscernImage() { string filename = FileHelper.OpenImageFile(); if (string.IsNullOrEmpty(filename)) return; Image image = Image.FromFile(filename); picImage.Image = image; _imageFilePath = filename; } private void OpenCV() { if (string.IsNullOrEmpty(_imageFilePath)) return; Mat srcImage = new Mat(_imageFilePath, ImreadModes.Color); if (srcImage.Empty()) { return; } //圖像轉換為灰度圖像 Mat grayImage = new Mat(); OpencvHelper.CvGrayImage(srcImage, grayImage); ShowImage("灰度圖像", grayImage); //OpencvHelper.RotateImage(grayImage, grayImage, 50, 1); //OpencvHelper.Imshow("旋轉", grayImage); //建立圖像的梯度幅值 Mat gradientImage = new Mat(); OpencvHelper.CvConvertScaleAbs(grayImage, gradientImage); ShowImage("梯度幅值", gradientImage); //對圖片進行相應的模糊化,使一些噪點消除 Mat blurImage = new Mat(); Mat thresholdImage = new Mat(); OpencvHelper.BlurImage(gradientImage, blurImage, thresholdImage); ShowImage("二值化", blurImage); //二值化以后的圖像,條形碼之間的黑白沒有連接起來,就要進行形態學運算,消除縫隙,相當於小型的黑洞,選擇閉運算 //因為是長條之間的縫隙,所以需要選擇寬度大於長度 Mat morphImage = new Mat(); OpencvHelper.MorphImage(thresholdImage, morphImage); ShowImage("閉運算", morphImage); //現在要讓條形碼區域連接在一起,所以選擇膨脹腐蝕,而且為了保持圖形大小基本不變,應該使用相同次數的膨脹腐蝕 //先腐蝕,讓其他區域的亮的地方變少最好是消除,然后膨脹回來,消除干擾,迭代次數根據實際情況選擇 OpencvHelper.DilationErosionImage(morphImage); ShowImage("膨脹腐蝕", morphImage); Mat[] contours = new Mat[10000]; List<double> OutArray = new List<double>(); //接下來對目標輪廓進行查找,目標是為了計算圖像面積 Cv2.FindContours(morphImage, out contours, OutputArray.Create(OutArray), RetrievalModes.External, ContourApproximationModes.ApproxSimple); //看看輪廓圖像 //Cv2.DrawContours(srcImage, contours, -1, Scalar.Yellow); //OpencvHelper.Imshow("目標輪廓", srcImage); //計算輪廓的面積並且存放 for (int i = 0; i < OutArray.Count; i++) { OutArray[i] = contours[i].ContourArea(false); } List<string> codes = new List<string>(); int num = 0; while (num < 10) //找出10個面積最大的矩形 { //找出面積最大的輪廓 double minValue, maxValue; OpenCvSharp.Point minLoc, maxLoc; Cv2.MinMaxLoc(InputArray.Create(OutArray), out minValue, out maxValue, out minLoc, out maxLoc); //計算面積最大的輪廓的最小的外包矩形 RotatedRect minRect = Cv2.MinAreaRect(contours[maxLoc.Y]); //找到了矩形的角度,但是這是一個旋轉矩形,所以還要重新獲得一個外包最小矩形 Rect myRect = Cv2.BoundingRect(contours[maxLoc.Y]); //將掃描的圖像裁剪下來,並保存為相應的結果,保留一些X方向的邊界,所以對rect進行一定的擴張 myRect.X = myRect.X - (myRect.Width / 20); myRect.Width = (int)(myRect.Width * 1.1); //TermCriteria termc = new TermCriteria(CriteriaType.MaxIter, 1, 1); //Cv2.CamShift(srcImage, myRect, termc); //一次最大面積的 var a = contours.ToList(); a.Remove(contours[maxLoc.Y]); contours = a.ToArray(); OutArray.Remove(OutArray[maxLoc.Y]); string code = DiscernBarCode(srcImage, myRect); if(!string.IsNullOrEmpty(code)) { //Cv2.Rectangle(srcImage, myRect, new Scalar(0, 255, 255), 3, LineTypes.AntiAlias); codes.Add(code); } Cv2.Rectangle(srcImage, myRect, new Scalar(0, 255, 255), 3, LineTypes.AntiAlias); num++; if (contours.Count() <= 0) break; } Image img2 = CreateImage(srcImage); picFindContours.Image = img2; txtcodess.Text = string.Join("\r\n", codes); ////找出面積最大的輪廓 //double minValue, maxValue; //OpenCvSharp.Point minLoc, maxLoc; //Cv2.MinMaxLoc(InputArray.Create(OutArray), out minValue, out maxValue, out minLoc, out maxLoc); ////計算面積最大的輪廓的最小的外包矩形 //RotatedRect minRect = Cv2.MinAreaRect(contours[maxLoc.Y]); ////為了防止找錯,要檢查這個矩形的偏斜角度不能超標 ////如果超標,那就是沒找到 //if (minRect.Angle < 2.0) //{ // //找到了矩形的角度,但是這是一個旋轉矩形,所以還要重新獲得一個外包最小矩形 // Rect myRect = Cv2.BoundingRect(contours[maxLoc.Y]); // //把這個矩形在源圖像中畫出來 // //Cv2.Rectangle(srcImage, myRect, new Scalar(0, 255, 255), 3, LineTypes.AntiAlias); // //看看顯示效果,找的對不對 // //Imshow("裁剪圖片", srcImage); // //將掃描的圖像裁剪下來,並保存為相應的結果,保留一些X方向的邊界,所以對rect進行一定的擴張 // myRect.X = myRect.X - (myRect.Width / 20); // myRect.Width = (int)(myRect.Width * 1.1); // Mat resultImage = new Mat(srcImage, myRect); // //OpencvHelper.Imshow("結果圖片", resultImage); // Image img = CreateImage(resultImage); // picCode.Image = img; // DiscernBarcode(img); // //看看輪廓圖像 // Cv2.DrawContours(srcImage, contours, -1, Scalar.Red); // //把這個矩形在源圖像中畫出來 // Cv2.Rectangle(srcImage, myRect, new Scalar(0, 255, 255), 3, LineTypes.AntiAlias); // Image img2 = CreateImage(srcImage); // picFindContours.Image = img2; // //string path = Path.GetDirectoryName(@g_sFilePath) + "\\Ok.png"; // //if (File.Exists(@path)) File.Delete(@path);//如果文件存在 則刪除 // //if (!Cv2.ImWrite(@path, resultImage)) //} srcImage.Dispose(); } private void HandelCode(Mat srcImage, Rect myRect, Mat[] contours) { Mat resultImage = new Mat(srcImage, myRect); Image img = CreateImage(resultImage); picCode.Image = img; DiscernBarcode(img); //看看輪廓圖像 Cv2.DrawContours(srcImage, contours, -1, Scalar.Red); //把這個矩形在源圖像中畫出來 Cv2.Rectangle(srcImage, myRect, new Scalar(0, 255, 255), 3, LineTypes.AntiAlias); //Image img2 = CreateImage(srcImage); //picFindContours.Image = img2; } private Image CreateImage(Mat resultImage) { byte[] bytes = resultImage.ToBytes(); MemoryStream ms = new MemoryStream(bytes); return Bitmap.FromStream(ms, true); } private void ShowImage(string name, Mat resultImage) { //Image img = CreateImage(resultImage); //frmShowImage frm = new frmShowImage(name, img); //frm.ShowDialog(); } /// <summary> /// 解析條形碼圖片 /// </summary> private string DiscernBarCode(Mat srcImage, Rect myRect) { try { Mat resultImage = new Mat(srcImage, myRect); Image img = CreateImage(resultImage); Bitmap pImg = MakeGrayscale3((Bitmap)img); BarcodeReader reader = new BarcodeReader(); reader.Options.CharacterSet = "UTF-8"; Result result = reader.Decode(new Bitmap(pImg)); Console.Write(result); if (result != null) return result.ToString(); else return ""; } catch (Exception ex) { Console.Write(ex); return ""; } } /// <summary> /// 解析條形碼圖片 /// </summary> private void DiscernBarcode(Image primaryImage) { //Bitmap pImg = MakeGrayscale3((Bitmap)primaryImage); picHandel.Image = primaryImage; BarcodeReader reader = new BarcodeReader(); reader.Options.CharacterSet = "UTF-8"; Result result = reader.Decode(new Bitmap(primaryImage));//Image.FromFile(path) Console.Write(result); if (result != null) txtBarCode.Text = result.ToString(); else txtBarCode.Text = ""; //watch.Start(); //watch.Stop(); //TimeSpan timeSpan = watch.Elapsed; //MessageBox.Show("掃描執行時間:" + timeSpan.TotalMilliseconds.ToString()); //using (ZBar.ImageScanner scanner = new ZBar.ImageScanner()) //{ // scanner.SetConfiguration(ZBar.SymbolType.None, ZBar.Config.Enable, 0); // scanner.SetConfiguration(ZBar.SymbolType.CODE39, ZBar.Config.Enable, 1); // scanner.SetConfiguration(ZBar.SymbolType.CODE128, ZBar.Config.Enable, 1); // List<ZBar.Symbol> symbols = new List<ZBar.Symbol>(); // symbols = scanner.Scan((Image)pImg); // if (symbols != null && symbols.Count > 0) // { // //string result = string.Empty; // //symbols.ForEach(s => result += "條碼內容:" + s.Data + " 條碼質量:" + s.Type + Environment.NewLine); // txtBarCode.Text = symbols.FirstOrDefault().Data; // } // else // { // txtBarCode.Text = ""; // } //} }
截圖出來的條形碼進行灰度處理
/// <summary> /// 處理圖片灰度 /// </summary> /// <param name="original"></param> /// <returns></returns> public static Bitmap MakeGrayscale3(Bitmap original) { //create a blank bitmap the same size as original Bitmap newBitmap = new Bitmap(original.Width, original.Height); //get a graphics object from the new image Graphics g = Graphics.FromImage(newBitmap); //create the grayscale ColorMatrix System.Drawing.Imaging.ColorMatrix colorMatrix = new System.Drawing.Imaging.ColorMatrix( new float[][] { new float[] {.3f, .3f, .3f, 0, 0}, new float[] {.59f, .59f, .59f, 0, 0}, new float[] {.11f, .11f, .11f, 0, 0}, new float[] {0, 0, 0, 1, 0}, new float[] {0, 0, 0, 0, 1} }); //create some image attributes ImageAttributes attributes = new ImageAttributes(); //set the color matrix attribute attributes.SetColorMatrix(colorMatrix); //draw the original image on the new image //using the grayscale color matrix g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height), 0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes); //dispose the Graphics object g.Dispose(); return newBitmap; }
效果圖: