opencv +数字识别
现在很多场景需要使用的数字识别,比如银行卡识别,以及车牌识别等,在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 识别算法原理
- 比如下面一张图片,需要从中识别出正确的数字,需要对图片进行灰度、二值化、腐蚀、膨胀、寻找数字轮廓、切割等一系列操作.
原图
灰度化图
二值化图
寻找轮廓
识别后的结果图
以上就是简单的图片进行灰度化、二值化、寻找数字轮廓得到的识别结果(这是基于我之前训练过的数字模型下得到的识别结果)
有些图片比较赋值,比如存在背景斜杠等的图片则需要一定的腐蚀或者膨胀等处理,才能寻找到正确的数字轮廓.
上面的说到我这里使用的是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