主頁 >  其他 > opencv +數字識別

opencv +數字識別

2020-09-17 07:08:56 其他

現在很多場景需要使用的數字識別,比如銀行卡識別,以及車牌識別等,在AI領域有很多影像識別演算法,大多是居于opencv 或者谷歌開源的tesseract 識別.

由于公司業務需要,需要開發一個客戶端程式,同時需要在xp這種老古董的機子上運行,故研究了如下幾個數字識別方案:

ocr 識別的不同選擇方案

  • tesseract
    • 放棄:谷歌的開源tesseract ocr識別目前最新版本不支持xp系統
  • 云端ocr 識別介面(不適用)
    • 費用比較貴:
    • 場景不同,我們的需求是可能毫秒級別就需要呼叫一次ocr 識別
  • opencv
  • 概念:OpenCV是一個基于BSD許可(開源)發行的跨平臺計算機視覺庫,可以運行在Linux、Windows、Android和Mac OS作業系統上,它輕量級而且高效——由一系列 C 函式和少量 C++ 類構成,同時提供了Python、Ruby、MATLAB等語言的介面,實作了影像處理和計算機視覺方面的很多通用演算法,

以上幾種ocr 識別比較,最后選擇了opencv 的方式進行ocr 數字識別,下面講解通過ocr識別的基本流程和演算法.

opencv 數字識別流程及演算法決議

要通過opencv 進行數字識別離不開訓練庫的支持,需要對目標圖片進行大量的訓練,才能做到精準的識別出目標數字;下面我會分別講解圖片訓練的程序及識別的程序.

opencv 識別演算法原理

  1. 比如下面一張圖片,需要從中識別出正確的數字,需要對圖片進行灰度、二值化、腐蝕、膨脹、尋找數字輪廓、切割等一系列操作.

原圖

image

灰度化圖

image

二值化圖

image

尋找輪廓

image

識別后的結果圖

image

以上就是簡單的圖片進行灰度化、二值化、尋找數字輪廓得到的識別結果(這是基于我之前訓練過的數字模型下得到的識別結果)
有些圖片比較賦值,比如存在背景斜杠等的圖片則需要一定的腐蝕或者膨脹等處理,才能尋找到正確的數字輪廓.

上面的說到我這里使用的是opencv 影像處理庫進行的ocr 識別,那我這里簡單介紹下C# 怎么使用opencv 影像處理看;

為了在xp上能夠運行 我這里通過nuget 包參考了 OpenCvSharp-AnyCPU 第三方庫,它使用的是opencv 2410 版本,你們如果不考慮xp系統的情況下開源使用最新的版本,最新版本支持了更多的識別演算法.

右擊你的個人專案,選擇“管理Nuget程式包”,在包管理器頁面中,點擊“瀏覽”選項,然后在搜索框中鍵入“OpenCvSharp-AnyCPU”,選擇最頂端的正確專案,并在右側詳情頁中點擊“安裝”,等待安裝完成即可,

以上的核心代碼如下:

      private void runSimpleOCR(string pathName)
       {
            //構造opcvOcr 庫,這里的是我單獨對opencv 庫進行的一次封裝,加載訓練庫模板
            var opencvOcr = new OpencvOcr($"{path}Template\\Traindata.xml", opencvOcrConfig: new OCR.Model.OpencvOcrConfig()
            {
                ErodeLevel = 2.5,
                ThresholdType = OpenCvSharp.ThresholdType.Binary,
                ZoomLevel = 2,
            });

            var img = new Bitmap(this.txbFilaName.Text);

            var mat = img.ToMat();
            
            //核心識別方法
            var str = opencvOcr.GetText(mat, isDebug: true);
            this.labContent.Content = str;
        }

opencvOcr 的核心代碼如下


        #region Constructor

        const double Thresh = 80;
        const double ThresholdMaxVal = 255;
        const int _minHeight = 35;
        bool _isDebug = false;
        CvKNearest _cvKNearest = null;
        OpencvOcrConfig _config = new OpencvOcrConfig() { ZoomLevel = 2, ErodeLevel = 3 };
        #endregion

        /// <summary>
        /// 建構式
        /// </summary>
        /// <param name="path">訓練庫完整路徑</param>
        /// <param name="opencvOcrConfig">OCR相關配置資訊</param>
        public OpencvOcr(string path, OpencvOcrConfig opencvOcrConfig = null)
        {
            if (string.IsNullOrEmpty(path))
                throw new ArgumentNullException("path is not null");

            if (opencvOcrConfig != null)
                _config = opencvOcrConfig;

            this.LoadKnearest(path);
        }
        
        /// <summary>
        /// 加載Knn 訓練庫模型
        /// </summary>
        /// <param name="dataPathFile"></param>
        /// <returns></returns>
        private CvKNearest LoadKnearest(string dataPathFile)
        {
            if (_cvKNearest == null)
            {

                using (var fs = new FileStorage(dataPathFile, FileStorageMode.Read))
                {
                    var samples = fs["samples"].ReadMat();
                    var responses = fs["responses"].ReadMat();
                    this._cvKNearest = new CvKNearest();
                    this._cvKNearest.Train(samples, responses);
                }
            }
            return _cvKNearest;
        }

        /// <summary>
        /// OCR 識別,僅僅只能識別單行數字 
        /// </summary>
        /// <param name="kNearest">訓練庫</param>
        /// <param name="path">要識別的圖片路徑</param>
        public override string GetText(Mat src, bool isDebug = false)
        {
            this._isDebug = isDebug;

            #region 圖片處理
            var respMat = MatProcessing(src, isDebug);
            if (respMat == null)
                return "";
            #endregion

            #region 查找輪廓
            var sortRect = FindContours(respMat.FindContoursMat);
            #endregion

            return GetText(sortRect, respMat.ResourcMat, respMat.RoiResultMat);
        }
        
         /// <summary>
        /// 查找輪廓
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        private List<Rect> FindContours(Mat src)
        {
            try
            {
                #region 查找輪廓
                Point[][] contours;
                HierarchyIndex[] hierarchyIndexes;
                Cv2.FindContours(
                    src,
                    out contours,
                    out hierarchyIndexes,
                    mode: OpenCvSharp.ContourRetrieval.External,
                    method: OpenCvSharp.ContourChain.ApproxSimple);

                if (contours.Length == 0)
                    throw new NotSupportedException("Couldn't find any object in the image.");
                #endregion

                #region 單行排序(目前僅僅支持單行文字,多行文字順序可能不對,按照x坐標進行排序)
                var sortRect = GetSortRect(contours, hierarchyIndexes);
                sortRect = sortRect.OrderBy(item => item.X).ToList();
                #endregion

                return sortRect;
            }
            catch { }

            return null;
        }
        
        /// <summary>
        /// 獲得切割后的數量串列
        /// </summary>
        /// <param name="contours"></param>
        /// <param name="hierarchyIndex"></param>
        /// <returns></returns>
        private List<Rect> GetSortRect(Point[][] contours, HierarchyIndex[] hierarchyIndex)
        {
            var sortRect = new List<Rect>();

            var _contourIndex = 0;
            while ((_contourIndex >= 0))
            {
                var contour = contours[_contourIndex];
                var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour

                sortRect.Add(boundingRect);
                _contourIndex = hierarchyIndex[_contourIndex].Next;
            }
            return sortRect;
        }


        /// <summary>
        /// 是否放大
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        private bool IsZoom(Mat src)
        {
            if (src.Height <= _minHeight)
                return true;

            return false;
        }
        

        private List<EnumMatAlgorithmType> GetAlgoritmList(Mat src)
        {
            var result = new List<EnumMatAlgorithmType>();
            var algorithm = this._config.Algorithm;

            #region 自定義的演算法
            try
            {
                if (algorithm.Contains("|"))
                {
                    result = algorithm.Split('|').ToList()
                        .Select(item => (EnumMatAlgorithmType)Convert.ToInt32(item))
                        .ToList();

                    if (!IsZoom(src))
                        result.Remove(EnumMatAlgorithmType.Zoom);

                    return result;
                }
            }
            catch { }

            #endregion

            #region 默認演算法
            if (IsZoom(src))
            {
                result.Add(EnumMatAlgorithmType.Zoom);
            }
            if (this._config.ThresholdType == ThresholdType.Binary)
            {
                //result.Add(EnumMatAlgorithmType.Blur);

                result.Add(EnumMatAlgorithmType.Gray);
                result.Add(EnumMatAlgorithmType.Thresh);
                if (this._config.DilateLevel > 0)
                    result.Add(EnumMatAlgorithmType.Dilate);

                result.Add(EnumMatAlgorithmType.Erode);
                return result;
            }
            //result.Add(EnumMatAlgorithmType.Blur);

            result.Add(EnumMatAlgorithmType.Gray);
            result.Add(EnumMatAlgorithmType.Thresh);
            if (this._config.DilateLevel > 0)
                result.Add(EnumMatAlgorithmType.Dilate);

            result.Add(EnumMatAlgorithmType.Erode);
            return result;
            #endregion
        }


        /// <summary>
        /// 對查找的輪廓資料進行訓練模型匹配,這里使用的是KNN 匹配演算法
        /// </summary>
        private string GetText(List<Rect> sortRect, Mat source, Mat roiSource)
        {
            var response = "";
            try
            {
                if ((sortRect?.Count ?? 0) <= 0)
                    return response;

                var contourIndex = 0;
                using (var dst = new Mat(source.Rows, source.Cols, MatType.CV_8UC3, Scalar.All(0)))
                {
                    sortRect.ForEach(boundingRect =>
                    {
                        try
                        {
                            #region 繪制矩形
                            if (this._isDebug)
                            {
                                Cv2.Rectangle(source, new Point(boundingRect.X, boundingRect.Y),
                                new Point(boundingRect.X + boundingRect.Width, boundingRect.Y + boundingRect.Height),
                                new Scalar(0, 0, 255), 1);

                                Cv2.Rectangle(roiSource, new Point(boundingRect.X, boundingRect.Y),
                                   new Point(boundingRect.X + boundingRect.Width, boundingRect.Y + boundingRect.Height),
                                   new Scalar(0, 0, 255), 1);
                            }
                            #endregion

                            #region 單個ROI
                            var roi = roiSource.GetROI(boundingRect); //Crop the image
                            roi = roi.Compress();
                            var result = roi.ConvertFloat();
                            #endregion

                            #region KNN 匹配
                            var results = new Mat();
                            var neighborResponses = new Mat();
                            var dists = new Mat();
                            var detectedClass = (int)this._cvKNearest.FindNearest(result, 1, results, neighborResponses, dists);
                            var resultText = detectedClass.ToString(CultureInfo.InvariantCulture);
                            #endregion

                            #region 匹配
                            var isDraw = false;
                            if (detectedClass >= 0)
                            {
                                response += detectedClass.ToString();
                                isDraw = true;
                            }
                            if (detectedClass == -1 && !response.Contains("."))
                            {
                                response += ".";
                                resultText = ".";
                                isDraw = true;
                            }
                            #endregion

                            #region 繪制及輸出切割資訊庫
                            try
                            {
                                //if (this._isDebug)
                                //{
                                Write(contourIndex, detectedClass, roi);
                                //}
                            }
                            catch { }

                            if (this._isDebug && isDraw)
                            {
                                Cv2.PutText(dst, resultText, new Point(boundingRect.X, boundingRect.Y + boundingRect.Height), 0, 1, new Scalar(0, 255, 0), 2);
                            }
                            #endregion

                            result?.Dispose();
                            results?.Dispose();
                            neighborResponses?.Dispose();
                            dists?.Dispose();
                            contourIndex++;
                        }
                        catch (Exception ex)
                        {
                            TextHelper.Error("GetText ex", ex);
                        }
                    });

                    #region 除錯模式顯示程序
                    source.IsDebugShow("Segmented Source", this._isDebug);
                    dst.IsDebugShow("Detected", this._isDebug);
                    dst.IsDebugWaitKey(this._isDebug);
                    dst.IsDebugImWrite("dest.jpg", this._isDebug);
                    #endregion
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                source?.Dispose();
                roiSource?.Dispose();
            }
            return response;
        }
        
        /// <summary>
        /// 圖片處理演算法
        /// </summary>
        /// <param name="src"></param>
        /// <param name="isDebug"></param>
        /// <returns></returns>
        public ImageProcessModel MatProcessing(Mat src, bool isDebug = false)
        {
            src.IsDebugShow("原圖", isDebug);

            var list = GetAlgoritmList(src);
            var resultMat = new Mat();
            src.CopyTo(resultMat);
            var isZoom = IsZoom(src);
            list?.ForEach(item =>
            {
                switch (item)
                {
                    case EnumMatAlgorithmType.Dilate:
                        resultMat = resultMat.ToDilate(Convert.ToInt32(this._config.DilateLevel));
                        resultMat.IsDebugShow(EnumMatAlgorithmType.Dilate.GetDescription(), isDebug);
                        break;
                    case EnumMatAlgorithmType.Erode:
                        var eroderLevel = isZoom ? this._config.ErodeLevel * this._config.ZoomLevel : this._config.ErodeLevel;
                        resultMat = resultMat.ToErode(eroderLevel);
                        resultMat.IsDebugShow(EnumMatAlgorithmType.Erode.GetDescription(), isDebug);
                        break;
                    case EnumMatAlgorithmType.Gray:
                        resultMat = resultMat.ToGrey();
                        resultMat.IsDebugShow(EnumMatAlgorithmType.Gray.GetDescription(), isDebug);
                        break;
                    case EnumMatAlgorithmType.Thresh:
                        var thresholdValue = https://www.cnblogs.com/dearroy/p/this._config.ThresholdValue <= 0 ? resultMat.GetMeanThreshold() : this._config.ThresholdValue;
                        resultMat = resultMat.ToThreshold(thresholdValue, thresholdType: this._config.ThresholdType);
                        resultMat.IsDebugShow(EnumMatAlgorithmType.Thresh.GetDescription(), isDebug);
                        break;
                    case EnumMatAlgorithmType.Zoom:
                        resultMat = resultMat.ToZoom(this._config.ZoomLevel);
                        src = resultMat;
                        resultMat.IsDebugShow(EnumMatAlgorithmType.Zoom.GetDescription(), isDebug);
                        break;
                    case EnumMatAlgorithmType.Blur:
                        resultMat = resultMat.ToBlur();
                        src = resultMat;
                        resultMat.IsDebugShow(EnumMatAlgorithmType.Blur.GetDescription(), isDebug);
                        break;
                }
            });

            var oldThreshImage = new Mat();
            resultMat.CopyTo(oldThreshImage);

            return new ImageProcessModel()
            {
                ResourcMat = src,
                FindContoursMat = oldThreshImage,
                RoiResultMat = resultMat
            };
        }

opencv 圖片處理開放出去的配置物件物體如下:

 public class OpencvOcrConfig
    {
        /// <summary>
        /// 放大程度級別 默認2
        /// </summary>
        public double ZoomLevel { set; get; }

        /// <summary>
        /// 腐蝕級別 默認2.5
        /// </summary>
        public double ErodeLevel { set; get; }

        /// <summary>
        /// 膨脹
        /// </summary>
        public double DilateLevel { set; get; }

        /// <summary>
        /// 閥值
        /// </summary>
        public double ThresholdValue { set; get; }

        /// <summary>
        /// 圖片處理演算法,用逗號隔開
        /// </summary>
        public string Algorithm { set; get; }

        /// <summary>
        /// 二值化方式
        /// </summary>
        public ThresholdType ThresholdType { set; get; } = ThresholdType.BinaryInv;

        /// <summary>
        /// 通道模式
        /// </summary>
        public OcrChannelTypeEnums ChannelType { set; get; } = OcrChannelTypeEnums.BlackBox;

    }

opencv 圖片處理演算法擴展方法如下:

 public static partial class OpenCvExtensions
    {
        private const int Thresh = 200;
        private const int ThresholdMaxVal = 255;

        /// <summary>
        /// Bitmap Convert Mat
        /// </summary>
        /// <param name="bitmap"></param>
        /// <returns></returns>
        public static Mat ToMat(this System.Drawing.Bitmap bitmap)
        {
            return OpenCvSharp.Extensions.BitmapConverter.ToMat(bitmap);
        }

        /// <summary>
        /// Bitmap Convert Mat
        /// </summary>
        /// <param name="bitmap"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap ToBitmap(this Mat mat)
        {
            return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat);
        }


        public static bool MatIsEqual(this Mat mat1, Mat mat2)
        {
            try
            {
                if (mat1.Empty() && mat2.Empty())
                {
                    return true;
                }
                if (mat1.Cols != mat2.Cols || mat1.Rows != mat2.Rows || mat1.Dims() != mat2.Dims() ||
                    mat1.Channels() != mat2.Channels())
                {
                    return false;
                }
                if (mat1.Size() != mat2.Size() || mat1.Type() != mat2.Type())
                {
                    return false;
                }
                var nrOfElements1 = mat1.Total() * mat1.ElemSize();
                if (nrOfElements1 != mat2.Total() * mat2.ElemSize())
                    return false;

                return MatPixelEqual(mat1, mat2);
            }
            catch (Exception ex)
            {
                TextHelper.Error("MatIsEqual 例外", ex);
                return true;
            }
        }

        /// <summary>
        /// 灰度
        /// </summary>
        /// <param name="mat"></param>
        /// <returns></returns>
        public static Mat ToGrey(this Mat mat)
        {
            try
            {
                Mat grey = new Mat();
                Cv2.CvtColor(mat, grey, OpenCvSharp.ColorConversion.BgraToGray);
                return grey;
            }
            catch
            {
                return mat;
            }
        }

        /// <summary>
        /// 二值化
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static Mat ToThreshold(this Mat data, double threshValue = https://www.cnblogs.com/dearroy/p/0, ThresholdType thresholdType = ThresholdType.BinaryInv)
        {
            Mat threshold = new Mat();

            if (threshValue == 0)
                threshValue = Thresh;
            Cv2.Threshold(data, threshold, threshValue, ThresholdMaxVal, thresholdType);
            if (threshold.IsBinaryInv())
            {
                Cv2.Threshold(threshold, threshold, threshValue, ThresholdMaxVal, ThresholdType.BinaryInv);
            }


            //Mat threshold = new Mat();

            //if (threshValue == 0)
            //    threshValue = Thresh;
            //Cv2.AdaptiveThreshold(data, threshold, ThresholdMaxVal,AdaptiveThresholdType.MeanC, thresholdType,3,0);
            //if (threshold.IsBinaryInv())
            //{
            //    Cv2.AdaptiveThreshold(threshold, threshold, ThresholdMaxVal, AdaptiveThresholdType.MeanC, ThresholdType.BinaryInv,3, 0);
            //}
            //Cv2.AdaptiveThreshold()
            // Threshold to find contour
            //var threshold = data.Threshold(80, 255, ThresholdType.BinaryInv);
            //Cv2.Threshold(data, threshold, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour

            //Cv2.AdaptiveThreshold(data, threshold, 255, AdaptiveThresholdType.MeanC, ThresholdType.BinaryInv, 11, 2);

            //Cv2.Threshold(data, data, Thresh, ThresholdMaxVal, OpenCvSharp.ThresholdType.BinaryInv); // Threshold to find contour
            //Cv2.AdaptiveThreshold(data, threshold, ThresholdMaxVal, AdaptiveThresholdType.GaussianC, OpenCvSharp.ThresholdType.Binary, 3, 0); // Threshold to find contour
            //Cv2.AdaptiveThreshold(data, threshold, 255, AdaptiveThresholdType.MeanC, ThresholdType.Binary, 3, 0);
            //CvInvoke.AdaptiveThreshold(data, data, 255, Emgu.CV.CvEnum.AdaptiveThresholdType.GaussianC, Emgu.CV.CvEnum.ThresholdType.Binary, 3, 0);
            return threshold;
            //var mat = data.Threshold(100, 255,ThresholdType.Binary);
            //return mat;
        }

        /// 
        /// 是否除錯顯示
        /// 
        /// 
        /// 
        /// 
        public static void IsDebugShow(this Mat src, string name, bool isDebug = false)
        {
            if (!isDebug)
                return;

            Cv2.ImShow(name, src);
        }

        public static void IsDebugWaitKey(this Mat src, bool isDebug = false)
        {
            if (!isDebug)
                return;

            Cv2.WaitKey();
        }

        public static void IsDebugImWrite(this Mat src, string path, bool isDebug = false)
        {
            if (!isDebug)
                return;

            try
            {
                Cv2.ImWrite(path, src);
            }
            catch { }
        }

        /// 
        /// Mat 轉成另外一種存盤矩陣方式
        /// 
        /// 
        /// 
        public static Mat ConvertFloat(this Mat roi)
        {
            var resizedImage = new Mat();
            var resizedImageFloat = new Mat();
            Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10
            resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float
            var result = resizedImageFloat.Reshape(1, 1);
            return result;
        }

        /// 
        /// 腐蝕
        /// 
        /// 
        /// 
        public static Mat ToErode(this Mat mat, double level)
        {

            #region level 2.5時默認的,自動會判斷是否需要腐蝕
            if (level < 1)
            {
                return mat;
            }
            if (level == 2.5)
            {
                if (!mat.IsErode())
                    return mat;
            }
            #endregion

            var erode = new Mat();

            var copyMat = new Mat();
            mat.CopyTo(copyMat);

            Cv2.Erode(mat, erode, Cv2.GetStructuringElement(StructuringElementShape.Ellipse, new Size(level, level)));
            return erode;
        }

        /// 
        /// 膨脹
        /// 
        /// 
        /// 
        public static Mat ToDilate(this Mat mat, int level)
        {
            if (level <= 0)
                return mat;
            var dilate = new Mat();
            Cv2.Dilate(mat, dilate, Cv2.GetStructuringElement(StructuringElementShape.Ellipse, new Size(level, level)));
            return dilate;
            //return mat;
        }

        /// 
        /// mat 轉Roi
        /// 
        /// 
        /// 
        /// 
        public static Mat GetROI(this Mat image, Rect boundingRect)
        {
            try
            {
                return new Mat(image, boundingRect); //Crop the image
            }
            catch
            {

            }
            return null;
        }

        /// 
        /// 獲取平均閥值
        /// 
        /// 
        /// 
        public static int GetMeanThreshold(this Mat mat)
        {
            var width = mat.Width;
            var height = mat.Height;

            var m = mat.Reshape(1, width * height);
            return (int)m.Sum() / (width * height);
        }

        /// 
        /// 獲得二值化閥值
        /// 
        /// 
        /// 
        public static int GetMeanThreshold(this System.Drawing.Bitmap bitmap)
        {
            using (var mat = bitmap.ToMat())
            using (var grap = mat.ToGrey())
            {
                return grap.GetMeanThreshold();
            }
        }

        public static bool IsErode(this System.Drawing.Bitmap bitmap)
        {
            using (var mat = bitmap.ToMat())
            using (var grap = mat.ToGrey())
            {

                var thresholdValue = grap.GetMeanThreshold();
                using (var threshold = grap.ToThreshold(thresholdValue, ThresholdType.BinaryInv))
                {
                    return threshold.IsErode();
                }
            }
        }

        /// 
        /// 放大
        /// 
        /// 
        /// 
        /// 
        public static Mat ToZoom(this Mat img, double times)
        {
            if (times <= 0)
                return img;
            var width = img.Width * times;
            var height = img.Height * times;

            img = img.Resize(new Size(width, height), 0, 0, Interpolation.NearestNeighbor);
            return img;
        }

        /// 
        /// 均值濾波
        /// 
        /// 
        /// 
        public static Mat ToBlur(this Mat img)
        {
            return img.Blur(new Size(3, 3));
        }

        public static Mat Compress(this Mat img)
        {
            var width = 28.0 * img.Width / img.Height;

            var fWidth = width / img.Width;
            var fHeight = 28.0 / img.Height;

            img = img.Resize(new Size(width, 28), fWidth, fHeight, Interpolation.NearestNeighbor);
            return img;
        }

        public static bool MatPixelEqual(this Mat src, Mat are)
        {
            var width = src.Width;
            var height = src.Height;
            var sum = width * height;

            for (int row = 0; row < height; row++)
            {
                for (int col = 0; col < width; col++)
                {
                    byte p = src.At(row, col); //獲對應矩陣坐標的取像素
                    byte pAre = are.At(row, col);
                    if (p != pAre)
                        return false;
                }
            }
            return true;
        }

        public static int GetSumPixelCount(this Mat threshold)
        {
            var width = threshold.Width;
            var height = threshold.Height;
            var sum = width * height;

            var value = 0;
            for (int row = 0; row < height; row++)
            {
                for (int col = 0; col < width; col++)
                {
                    byte p = threshold.At(row, col); //獲對應矩陣坐標的取像素
                    value++;
                }
            }
            return value;
        }

        public static int GetPixelCount(this Mat threshold, System.Drawing.Color color)
        {
            var width = threshold.Width;
            var height = threshold.Height;
            var sum = width * height;

            var value = 0;
            for (int row = 0; row < height; row++)
            {
                for (int col = 0; col < width; col++)
                {
                    byte p = threshold.At(row, col); //獲對應矩陣坐標的取像素
                    if (Convert.ToInt32(p) == color.R)
                    {
                        value++;
                    }
                }
            }
            return value;
        }

        /// 
        /// 是否需要二值化反轉
        /// 
        /// 
        /// 
        public static bool IsBinaryInv(this Mat threshold)
        {
            var width = threshold.Width;
            var height = threshold.Height;
            var sum = Convert.ToDouble(width * height);

            var black = GetPixelCount(threshold, System.Drawing.Color.Black);

            return (Convert.ToDouble(black) / sum) < 0.5;
        }

        /// 
        /// 是否需要腐蝕
        /// 
        /// 
        /// 
        public static bool IsErode(this Mat mat)
        {
            var percent = mat.GetPercent();
            return percent >= 0.20;
        }

        /// 
        /// 獲得白色像素占比
        /// 
        /// 
        /// 
        public static double GetPercent(this Mat threshold)
        {
            var width = threshold.Width;
            var height = threshold.Height;
            var sum = Convert.ToDouble(width * height);

            var white = GetPixelCount(threshold, System.Drawing.Color.White);
            return (Convert.ToDouble(white) / sum);
        }

        /// 
        /// 根據模板查找目標圖片的在原圖示中的開始位置坐標
        /// 
        /// 
        /// 
        /// 
        /// 
        public static Point FindTemplate(this Mat source, Mat template, MatchTemplateMethod matchTemplateMethod = MatchTemplateMethod.SqDiffNormed)
        {
            if (source == null)
                return new OpenCvSharp.CPlusPlus.Point();

            var result = new Mat();
            Cv2.MatchTemplate(source, template, result, matchTemplateMethod);

            Cv2.MinMaxLoc(result, out OpenCvSharp.CPlusPlus.Point minVal, out OpenCvSharp.CPlusPlus.Point maxVal);

            var topLeft = new OpenCvSharp.CPlusPlus.Point();
            if (matchTemplateMethod == MatchTemplateMethod.SqDiff || matchTemplateMethod == MatchTemplateMethod.SqDiffNormed)
            {
                topLeft = minVal;
            }
            else
            {
                topLeft = maxVal;
            }
            return topLeft;
        }
    }

以上代碼中開源對圖片進行輪廓切割,同時會生成切割后的圖片代碼如下

#region 繪制及輸出切割資訊庫
    try
    {

        Write(contourIndex, detectedClass, roi);

    }
    catch { }
#endregion

private void Write(int contourIndex, int detectedClass, Mat roi)
{
    Task.Factory.StartNew(() =>
    {
        try
        {
            var templatePath = $"{AppDomain.CurrentDomain.BaseDirectory}template";
            FileHelper.CreateDirectory(templatePath);
            var templatePathFile = $"{templatePath}/{contourIndex}_{detectedClass.ToString()}.png";
            Cv2.ImWrite(templatePathFile, roi);
            if (!roi.IsDisposed)
            {
                roi.Dispose();
            }
        }
        catch {}
   });
}

切割后的圖片如下:
image

這里我已經對數字進行切割好了,接下來就是需要對0-9 這些數字進行分類(建立檔案夾進行數字歸類),如下:
image

圖中的每一個分類都是我事先切割好的數字圖片,圖中有-1 和-2 這兩個特殊分類,-1 里面我是放的是“.”好的分類,用于訓練“.”的圖片,這樣就可以識別出小數點的數字支持.
-2 這個分類主要是其他一些無關緊要的圖片,也就是不是數字和點的都歸為這一類中.

現在訓練庫分類已經建立好了,接下來我們需要對這些分類數字進行歸一化處理,生成訓練模型. 代碼如下:

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            var opencvOcr = new OpencvOcr($"{path}Template\\Traindata.xml", opencvOcrConfig: null);
            opencvOcr.Save($"{path}Template\\NumberWrite", outputPath: $"{path}Template\\Traindata.xml");
            MessageBox.Show("生成訓練庫成功");
            //var img = new Bitmap(this.txbFilaName.Text);

            //var str = opencvOcr.GetText(img.ToMat(), isDebug: true);
            //this.labContent.Content = str;
        }
        
        /// <summary>
        /// 保存訓練模型
        /// </summary>
        /// <param name="dataPath"></param>
        /// <param name="trainExt"></param>
        /// <param name="dataPathFile"></param>
        public void Save(string dataPath, string trainExt = "*.png", string outputPath = "")
        {
            if (string.IsNullOrEmpty(outputPath))
                throw new ArgumentNullException("save dataPath is not null");

            var trainingImages = this.ReadTrainingImages(dataPath, trainExt);
            var samples = GetSamples(trainingImages);
            var response = GetResponse(trainingImages);

            //寫入到訓練庫中
            using (var fs = new FileStorage(outputPath, FileStorageMode.WriteText))
            {
                fs.Write("samples", samples);
                fs.Write("responses", response);
            }
        }

        /// <summary>
        /// 根據目錄加載檔案
        /// </summary>
        /// <param name="path"></param>
        /// <param name="ext"></param>
        /// <returns></returns>
        private IList<ImageInfo> ReadTrainingImages(string path, string ext)
        {
            var images = new List<ImageInfo>();
            var imageId = 1;
            foreach (var dir in new DirectoryInfo(path).GetDirectories())
            {
                var groupId = int.Parse(dir.Name);
                foreach (var imageFile in dir.GetFiles(ext))
                {
                    var srcMat = new Mat(imageFile.FullName, OpenCvSharp.LoadMode.GrayScale);
                    var image = srcMat.ConvertFloat();
                    if (image == null)
                    {
                        continue;
                    }

                    images.Add(new ImageInfo
                    {
                        Image = image,
                        ImageId = imageId++,
                        ImageGroupId = groupId
                    });
                }
            }
            return images;
        }
        
        /// <summary>
        /// Mat 轉成另外一種存盤矩陣方式
        /// </summary>
        /// <param name="roi"></param>
        /// <returns></returns>
        public static Mat ConvertFloat(this Mat roi)
        {
            var resizedImage = new Mat();
            var resizedImageFloat = new Mat();
            Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10
            resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float
            var result = resizedImageFloat.Reshape(1, 1);
            return result;
        }
        
        /// <summary>
        /// 獲取Samples
        /// </summary>
        /// <param name="trainingImages"></param>
        /// <returns></returns>
        private Mat GetSamples(IList<ImageInfo> trainingImages)
        {
            var samples = new Mat();
            foreach (var trainingImage in trainingImages)
            {
                samples.PushBack(trainingImage.Image);
            }
            return samples;
        }
        
        private Mat GetResponse(IList<ImageInfo> trainingImages)
        {
            var labels = trainingImages.Select(x => x.ImageGroupId).ToArray();
            var responses = new Mat(labels.Length, 1, MatType.CV_32SC1, labels);
            var tmp = responses.Reshape(1, 1); //make continuous
            var responseFloat = new Mat();
            tmp.ConvertTo(responseFloat, MatType.CV_32FC1); // Convert  to float

            return responses;
        }

到這里ocr 訓練模型以及建立好了,會在目錄中生成一個Traindata.xml 的訓練模型庫,我們來打開這個訓練模型庫檔案探索它的神秘的容顏.
image
image

到這里opencv + 數字識別分享已經完成,它的神秘面紗也就到此結束了

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/63574.html

標籤:其他

上一篇:【WPF學習】第四十五章 可視化物件

下一篇:asp.net core系列 75 Elasticsearch與中文分詞配置

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more