OpenVINO 手写字符识别模型与使用

引言

手写数字识别是一个很基础的模式识别问题,从传统特征提取加机器学习训练方法到深度神经网络训练的识别方法识别都会达到比较高的精度,同时手写数字识别也是一个特别不稳定,很难具备普适性的模型,需要针对性的数据集与训练,然后才得到比较好的识别精度。OpenVINO™ 在2021.4 版本中已经加入了手写数字识别的预训练模型,开始支持手写数字识别,以下面这张图(图-1)为例让我们一起探索与尝试下它的使用方法与效果如何!
OpenVINO 手写字符识别模型与使用
图-1 (原图)

模型介绍

在OpenVINO™ 2021.4版本中支持的手写数字识别模型为handwritten-score-recognition-0003

,支持 or . 格式的数字识别与小数点识别。该模型的结构有两个部分组成,前面是一个典型的CNN骨干网络,采用的是VGG-16类似的架构,实现特征提取;后面是一个双向的LSTM网络,实现序列预测;最终的预测结果基于CTC解析即可。其输入与输出格式如下:

输入格式为:[NCHW]= [1x1x32x64]

输出格式为:[WxBxL]=[16x1x13]

其中13表示"0123456789._#",#表示空白、_表示非数字的字符

对输出格式的解码方式支持CTC贪心与Beam搜索,演示程序使用CTC贪心解码,这种方式相对简单,前面一篇文章中我们已经详细介绍过了,后面就直接套用即可!

模型使用与演示

使用该模型必须是基于常见文字检测得到的ROI区域,然后转化为灰度图象,使用该模型完成预测,关于场景文字检测,在前面一篇文章中我已经详细交代过了,这里就不再赘述。这里小可脑洞大开,针对常见的文本图象,采用OpenCV二值图象轮廓分析来完成数字ROI区域得截取,同样取得了不错得效果。基本的流程是这样:

Step 1: 读取图象并二值化

代码如下

Mat src = imread("D:/images/zsxq/ocr.png");
imshow("input", src);
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 25, 10);

其中adaptiveThreshold函数实现对灰度图象自适应二值化,参数blockSize=25表示高斯窗口大小,constants=10表示自适应常量值。需要注意的是参数blockSize值必须为奇数。输出二值图象见图-2

OpenVINO 手写字符识别模型与使用
图-2(自适应二值化)

Step 2: 使用轮廓分析过填充过滤小噪点

代码如下

std::vector<vector<Point>> contours;
std::vector<Vec4i> hireachy;
findContours(binary, contours, hireachy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
int image_height = src.rows;
int image_width = src.cols;
for (size_t t = 0; t < contours.size(); t++) {
double area = contourArea(contours[t]);
if (area < 10) {
drawContours(binary, contours, t, Scalar(0), -1, 8);
}
}

上面的代码findContours表示轮廓发现,RETR_EXTERNAL表示采用发现最外层轮廓,CHAIN_APPROX_SIMPLE表示采用简单的链式编码收集轮廓上的像素点集。contourArea表示计算一个轮廓的面积,计算方式基于格林积分公式。drawContours表示绘制轮廓,其中thickness参数为-1表示填充,大于零表示绘制边缘。这里通过对白色噪点填充为黑色,完成噪声去除,下图是去干扰之后的图象:

OpenVINO 手写字符识别模型与使用
图-3(去噪之后)

Step 3:膨胀预处理

对第二步输出的图象我们不能直接通过轮廓发现截取ROI,然后交给数字识别网络去识别,原因是这样会导致ROI区域的宽高比失衡,导致输入的数字resize之后发现畸变,识别精度会降低,所以通过膨胀操作,把数字适度的加宽与加高,主要是加宽,这样保持输入ROI区域resize之后不变性,就很容易识别了。这部分预处理的代码如下:

Mat se = getStructuringElement(MORPH_RECT, Size(45, 5));
Mat temp;
dilate(binary, temp, se);

其中dilate表示膨胀操作、然后对得到temp图象。这步操作的输出如下:

OpenVINO 手写字符识别模型与使用
图-4(适度扩充之后)

Step 4:数字识别推理与解析

对图-4进行轮廓发现,截取ROI,遍历每个轮廓,调用识别推理即可输出。其中加载模型与获取推理请求,这里就不再赘述了,截取ROI与推理解析部分的代码如下:

// 处理输出结果
findContours(temp, contours, hireachy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (size_t t = 0; t < contours.size(); t++) {
Rect box = boundingRect(contours[t]);
Mat roi = gray(box);
size_t image_size = h*w;
Mat blob_image;
resize(roi, blob_image, Size(w, h));
// NCHW
unsigned char* data = static_cast<unsigned char*>(input->buffer());
for (size_t row = 0; row < h; row++) {
for (size_t col = 0; col < w; col++) {
data[row*w + col] = blob_image.at<uchar>(row, col);
}
}
// 执行预测
infer_request.Infer();
auto output = infer_request.GetBlob(output_name);
const float* blob_out = static_cast<PrecisionTrait<Precision::FP32>::value_type*>(output->buffer());
const SizeVector reco_dims = output->getTensorDesc().getDims();
const int RW = reco_dims[0];
const int RB = reco_dims[1];
const int RL = reco_dims[2];
std::string ocr_txt = ctc_decode(blob_out, RW, RL);
std::cout << ocr_txt << std::endl;
cv::putText(src, ocr_txt, box.tl(), cv::FONT_HERSHEY_PLAIN, 1.0, cv::Scalar(255, 0, 0), 1)
cv::rectangle(src, box, Scalar(0, 0, 255), 2, 8, 0);
}

首先进行轮廓发现,然后根据每个轮廓截取ROI区域,设置输入数据,推理,解析输出采用CTC方式,最终得到的输出如下图所示:

OpenVINO 手写字符识别模型与使用
图-5(运行结果)

扩展探索

这里我没有采用场景文字检测来获取ROI,而是采用传统的二值图象分析来完成,主要是避免跟前面的文字内容重叠,同时启发更多的思路。另外采用膨胀扩展,有时候并非是上上之选,还可以直接修改ROI大小来扩展,这部分其实可以参考上一篇场景文字识别的代码,图-6就来是二值化之后,轮廓发现获取外接矩形,直接修改ROI大小的方式,同时根据横纵比过滤非数字符号。改动部分就是去掉第三步膨胀,然后直接在第四步循环中添加下面的代码;

Rect box = boundingRect(contours[t]);
float rate = box.width / box.height;
if (rate > 1.5) {
continue;
}
box.x = box.x - 15;
box.width = box.width + 30;
box.y = box.y - 5;
box.height = box.height + 10;

OpenVINO 手写字符识别模型与使用
图-6

上一篇:c# 部署onnx模型


下一篇:【2.NVIDIA TensorRT官方文档简介-推理库】