opencv +数字识别

现在很多场景需要使用的数字识别,比如银行卡识别,以及车牌识别等,在AI领域有很多图像识别算法,大多是居于opencv 或者谷歌开源的tesseract 识别.

由于公司业务需要,需要开发一个客户端程序,同时需要在xp这种老古董的机子上运行,故研究了如下几个数字识别方案,如果大家有更好的方案可以留言告知我,大家一起学习借鉴,不过需要支持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.比如下面一张图片,需要从中识别出正确的数字,需要对图片进行灰度、二值化、腐蚀、膨胀、寻找数字轮廓、切割等一系列操作.

原图

灰度化图

二值化图

寻找轮廓

识别后的结果图

以上就是简单的图片进行灰度化、二值化、寻找数字轮廓得到的识别结果(==这是基于我之前训练过的数字模型下得到的识别结果==) 有些图片比较赋值,比如存在背景斜杠等的图片则需要一定的腐蚀或者膨胀等处理,才能寻找到正确的数字轮廓.

上面的说到我这里使用的是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 属性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);#endregionreturn 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();#endregionreturn 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 contoursortRect.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 单个ROIvar roi = roiSource.GetROI(boundingRect); //Crop the imageroi = 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);}#endregionresult?.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 = 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 = 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);}return threshold;}/// <summary>/// 是否调试显示/// </summary>/// <param name="src"></param>/// <param name="name"></param>/// <param name="isDebug"></param>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 { }}/// <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 10X10resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to floatvar result = resizedImageFloat.Reshape(1, 1);return result;}/// <summary>/// 腐蚀/// </summary>/// <param name="mat"></param>/// <returns></returns>public static Mat ToErode(this Mat mat, double level){#region 自动会判断是否需要腐蚀if (level < 1){return mat;}#endregionvar erode = new Mat();var copyMat = new Mat();mat.CopyTo(copyMat);Cv2.Erode(mat, erode, Cv2.GetStructuringElement(StructuringElementShape.Ellipse, new Size(level, level)));return erode;}/// <summary>/// 膨胀/// </summary>/// <param name="mat"></param>/// <returns></returns>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;}/// <summary>/// mat 转Roi/// </summary>/// <param name="image"></param>/// <param name="boundingRect"></param>/// <returns></returns>public static Mat GetROI(this Mat image, Rect boundingRect){try{return new Mat(image, boundingRect); //Crop the image}catch{}return null;}/// <summary>/// 获取平均阀值/// </summary>/// <param name="mat"></param>/// <returns></returns>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);}/// <summary>/// 获得二值化阀值/// </summary>/// <param name="bitmap"></param>/// <returns></returns>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();}}}/// <summary>/// 放大/// </summary>/// <param name="img"></param>/// <param name="times"></param>/// <returns></returns>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;}/// <summary>/// 均值滤波/// </summary>/// <param name="img"></param>/// <returns></returns>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<byte>(row, col); //获对应矩阵坐标的取像素byte pAre = are.At<byte>(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<byte>(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<byte>(row, col); //获对应矩阵坐标的取像素if (Convert.ToInt32(p) == color.R){value++;}}}return value;}/// <summary>/// 是否需要二值化反转/// </summary>/// <param name="threshold"></param>/// <returns></returns>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;}/// <summary>/// 是否需要腐蚀/// </summary>/// <param name="mat"></param>/// <returns></returns>public static bool IsErode(this Mat mat){var percent = mat.GetPercent();return percent >= 0.20;}/// <summary>/// 获得白色像素占比/// </summary>/// <param name="threshold"></param>/// <returns></returns>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);}/// <summary>/// 根据模板查找目标图片的在原图标中的开始位置坐标/// </summary>/// <param name="source"></param>/// <param name="template"></param>/// <param name="matchTemplateMethod"></param>/// <returns></returns>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 { }
#endregionprivate 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 {}});
}

切割后的图片如下:

这里我已经对数字进行切割好了,接下来就是需要对0-9 这些数字进行分类(建立文件夹进行数字归类),如下:

图中的每一个分类都是我事先切割好的数字图片,图中有-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 10X10resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to floatvar 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 continuousvar responseFloat = new Mat();tmp.ConvertTo(responseFloat, MatType.CV_32FC1); // Convert  to floatreturn responses;}

到这里ocr 训练模型以及建立好了,会在目录中生成一个Traindata.xml 的训练模型库,我们来打开这个训练模型库文件探索它的神秘的容颜.

如果大家有更好的数字识别方案可以留言告知我,这样可以更好的应用到实际场景中需求场景:

  1. 客户端调用,不走api方式

  2. 必须要xp这种老爷机的支持

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

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

相关文章

SQL(二)- 基础查询语句

简单的查询语句&#xff08;DQL&#xff09; 下面我们正式来学习查询语句&#xff0c;下面所有查询用到的表均为前面提到的三张表&#xff1a; 员工表中的数据&#xff1a; 部门表中的数据&#xff1a; 薪资表中的数据&#xff1a; 基本查询语句的语法&#xff1a; sele…

SQL(三)- 连接查询

连接查询概念 一、什么是连接查询&#xff1f; 在实际开发中&#xff0c;大部分的情况下都不是从单张表中查询数据&#xff0c;一般都是多张表联合查询最终取出最终结果。在实际再发中&#xff0c;一般一个业务都会对应多张表&#xff0c;比如学生和班级&#xff0c;最起码两…

远程办公也可以很高效

题图&#xff1a;我的站立办公环境因为疫情&#xff0c;全中国人民都过了一个难忘的春节&#xff0c;而身在武汉的我&#xff0c;更是没有出家门半步&#xff0c;坚决做到不过国家添乱。从开始的2月14到后来的2月20日&#xff0c;再到现在的3月10日&#xff0c;官方发布的复工日…

SQL(四) - 子查询和union以及limit分页

子查询概念 什么是子查询&#xff1f;子查询都可以出现在哪里&#xff1f; select语句当中嵌套select语句&#xff0c;被嵌套的select语句是子查询。 子查询可以出现在哪里&#xff1f; select..(select). from..(select). where..(select).1.where子句中使用子查询 案例&a…

ASP.NET Core中的Http缓存

ASP.NET Core中的Http缓存Http响应缓存可减少客户端或代理对 web服务器发出的请求数。响应缓存还减少了 web服务器生成响应所需的工作量。响应缓存由 Http请求中的 header控制。而 ASP.NETCore对其都有相应的实现&#xff0c;并不需要了解里面的工作细节&#xff0c;即可对其进…

SQL(五) - 表的创建以及操作

创建表 建表语句的语法格式&#xff1a; create table 表名(字段名1 数据类型,字段名2 数据类型,字段名3 数据类型,....);MySql常用数据类型 BLOB 二进制大对象&#xff08;存储图片、视频等流媒体信息&#xff09; Binary Large OBject &#xff08;对应java中的Object&…

Istio 2020 年 Roadmap——一切为了商用

原文地址&#xff1a;https://preliminary.istio.io/zh/blog/2020/tradewinds-2020/&#xff0c;由 ServiceMesher 社区翻译。Istio 解决了人们在运行微服务时遇到的实际问题。甚至早期的预发行版本就已经可以帮助用户诊断其体系架构中的延迟&#xff0c;提高服务的可靠性以及透…

SQL(七) - 事务、索引、视图

事务&#xff08;Transaction&#xff09; 3.1、什么是事务&#xff1f; 一个事务是一个完整的业务逻辑单元&#xff0c;不可再分。 比如&#xff1a;银行账户转账&#xff0c;从A账户向B账户转账10000.需要执行两条update语句&#xff1a; update t_act set balance balan…

如何编写高性能的C#代码(二)

使用Benchmark.NET对C# 代码进行基准测试的简介在我以前的文章中[10]&#xff0c;我介绍了该系列文章[11]&#xff0c;在其中我将分享我的经验&#xff0c;同时了解C&#xff03;和.NET Core&#xff08;corefx&#xff09;框架的新性能。在本文中&#xff0c;我想着重于对现有…

如何编写高性能的C#代码(一)

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权&#xff0c;请联系小编&#xff0c;小编将在24小时内删除。作者介绍&#xff1a;史蒂夫戈登&#xff08;Steve Gordon&#xff09;是Microsoft MVP&#xff0c;Pluralsight的作者&#xff0c;…

从Java转向.NET/C#,Are You OK?

最近由于项目变动&#xff0c;需要用.NET/C#做开发&#xff0c;经过一段时间的学习和培训&#xff0c;对这个技术栈有了一定的理解。大家可能都知道Java和.NET/C#很像&#xff0c;这里粗略的把两者做一个对比&#xff0c;希望对感兴趣的童鞋有所帮助。如果现在有人问我&#xf…

树的节点值之和

题目背景 墨家家主有棵树。 题目描述 给定一个保存树节点信息的数据结构&#xff0c;它包含了树节点唯一的 id &#xff0c;树节点值 和 直系子节点的 id 。 比如&#xff0c;树节点1是树节点2的父节点&#xff0c;树节点2是树节点3的父节点。他们相应的树节点值为 9 , 4 , …

.NET Core开发实战(第21课:中间件:掌控请求处理过程的关键)--学习笔记(上)...

21 | 中间件&#xff1a;掌控请求处理过程的关键这一节讲解一下如何通过中间件来管理请求处理过程中间件工作原理next 表示后面有一个委托&#xff0c;每一层每一层套下去可以在任意的中间件来决定在后面的中间件之前执行什么&#xff0c;或者说在所有中间件执行完之后执行什么…

简单的二叉树创建与遍历

编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以指针方式存储&#xff09;。 例如如下的先序遍历字符串&#xff1a; ABC##DE#G##F### 其中“#”表示的是空格&#xff0c;空格字符代表空树。建立起此二叉树以后&…

疫情期间,千万级系统宕机N次,老板撂下狠话:没法把性提升10倍,全员解雇!...

性能调优整体思路作为一名团队技术核心&#xff0c;如何让系统跑得通、跑得稳、跑得快是必然会面对的场景。性能分析是一个大课题&#xff0c;不同的架构、不同的应用场景、不同的程序语言分析的方法若有差异&#xff0c;抽象一下大致分为两类&#xff1a;自底向上&#xff1a;…

Anaconda创建python虚拟环境

在创建虚拟环境之前首先我们需要打开命令终端&#xff1a;Win R 输入cmd 或者直接打开Anaconda Prompt&#xff08;Anaconda&#xff09; pycharm下载历史版本地址&#xff1a;https://www.jetbrains.com/pycharm/download/other.html Anaconda下载历史版本地址&#xff1a;ht…

[蓝桥杯][算法提高VIP]夺宝奇兵-递推+记忆化搜索

题目描述 在一座山上,有很多很多珠宝,它们散落在山底通往山顶的每条道路上,不同道路上的珠宝的数目也各不相同.下图为一张藏宝地图: 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 ”夺宝奇兵”从山下出发,到达山顶,如何选路才能得到最多的珠宝呢?在上图所示例子中,按照5-> 7-> 8-&g…

梯度下降与线性回归

对于代价函数&#xff1a; loss∑i(y^−yi)2loss\sum_i{(\hat{y}-y_i)}^2loss∑i​(y^​−yi​)2 loss∑i(w∗xib−yi)2loss\sum_i{(w*x_ib-y_i)}^2loss∑i​(w∗xi​b−yi​)2 最常见的代价函数&#xff1a;均方差代价函数&#xff08;Mean-Square Error&#xff0c;MSE&…

.NET Core开发实战(第21课:中间件:掌控请求处理过程的关键)--学习笔记(下)...

21 | 中间件&#xff1a;掌控请求处理过程的关键如果在 Map 的时候逻辑复杂一点&#xff0c;不仅仅判断它的 URL 地址&#xff0c;而且要做特殊的判断的话&#xff0c;可以这么做把判断逻辑变成一个委托我们要判断当我们的请求地址包含 abc 的时候&#xff0c;输出 new abcapp.…

英伟达3060Ti安装GPU版本TensorFlow2.X和Pytorch

查看Python与TensorFlow对应版本 安装GPU版本的TensorFlow的时候&#xff0c;我们需要考虑的一个问题是Python版本与TensorFlow版本的对应关系&#xff0c;可以参考下面这个链接&#xff1a; Python对应TensorFlow CPU版本 GPU版本 查看显卡驱动对应的CUDA版本并且下载安装 …