windows c++&&c# 生成数字+大写字母的36个字符训练集
字符训练集生成源码
由于项目需要要完成OCR 字符识别,想要使用深度学习来训练字符分类,根据环境需求要完成多种字体,变形,缺失,字符颜色随机的情况下识别字符,所以想要自己生成符合需求的字数训练数据集。
1.筛选需要的字体种类,生成各种字体的各个字符的黑色背景白色字体的图片
使用查询c# Winform 查看windows 系统自带的字体,总共108中,谁选需要的字体(很多字体存在异形符号,同时考虑训练数据集的大小问题,筛除相似的字体)
遍历生成36个文件,和各种字体的字符。
示列代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace getChar36
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
int imgsize = 100;
int fontsize = 24;
string dirp = "Charimage";
private void button1_Click(object sender, EventArgs e)
{
//int[] nook = { 18, 19, 20, 21, 22, 23, 24, 28, 31, 33, 41, 56, 57, 62, 63, 74, 75, 76, 77, 78, 79, 64, 86, 81, 87 };
int[] nook = { 18, 19, 20, 21, 22, 23, 24, 28, 31, 33, 41, 56, 57, 62, 63,72, 74, 75,
76, 77, 78, 79, 64, 86, 81, 87,1,14,15,16,17,25,27,30,34,37,42,45,46,48,58,61,73,
80,82,85,88,89,90,95,97,101,102,104,105,106,107,32,66,26,29,52,53,54,55,83,96,94,100,60,84,103};不符合字体种类的id
int[] ok = { 0, 2, 3, 4, 6, 7, 51, 40, 36 };
for (int k = 0; k <= 9; k++)
{
string subPath = "D://" + dirp + "//" + k.ToString() + "//";
System.IO.Directory.CreateDirectory(subPath);
StringBuilder str = new StringBuilder(2000);
System.Drawing.Text.InstalledFontCollection fonts = new System.Drawing.Text.InstalledFontCollection();
int i = 0;
#region
foreach (System.Drawing.FontFamily family in fonts.Families)
{
bool isok = true;
for (int j = 0; j < nook.Length; j++)
{
if (nook[j] == i)
{
isok = false;
}
}
if (isok)
{
Rectangle rect = new Rectangle(0, 0, imgsize, imgsize);
Graphics dg = this.CreateGraphics();
Image image = new Bitmap(rect.Width, rect.Height, dg);
Graphics ig = Graphics.FromImage(image);
ig.FillRectangle(Brushes.Black, rect);
Font font = new Font(family.Name, fontsize);
Brush brush = System.Drawing.Brushes.White;
ig.DrawString(k.ToString(), font, brush, 15, 15);
// ImageFormat iformat = new ImageFormat(new Guid(Bmp));
image.Save("D://" + dirp + "//" + k.ToString() + "//" + k.ToString() + "_" + i.ToString() + ".jpg");
dg.DrawImage(image, new PointF(imgsize, imgsize));
}
i++;
}
#endregion
}
for (int k = 65; k <= 90; k++)
{
string subPath = "D://" + dirp + "//" + (k - 55).ToString() + "//";
System.IO.Directory.CreateDirectory(subPath);
StringBuilder str = new StringBuilder(2000);
System.Drawing.Text.InstalledFontCollection fonts = new System.Drawing.Text.InstalledFontCollection();
int i = 0;
#region
foreach (System.Drawing.FontFamily family in fonts.Families)
{
bool isok = true;
for (int j = 0; j < nook.Length; j++)
{
if (nook[j] == i)
{
isok = false;
}
}
if (isok)
{
Rectangle rect = new Rectangle(0, 0, imgsize, imgsize);
Graphics dg = this.CreateGraphics();
Image image = new Bitmap(rect.Width, rect.Height, dg);
Graphics ig = Graphics.FromImage(image);
ig.FillRectangle(Brushes.Black, rect);
Font font = new Font(family.Name, fontsize);
Brush brush = System.Drawing.Brushes.White;
ig.DrawString(NunberToChar(k), font, brush, 15, 15);
//ImageFormat iformat=new ImageFormat(new Guid(Bmp));
image.Save("D://" + dirp + "//" + (k - 55).ToString() + "//" + NunberToChar(k) + "_" + i.ToString() + ".jpg");
dg.DrawImage(image, new PointF(imgsize, imgsize));
}
i++;
}
#endregion
}
}
private string NunberToChar(int number)
{
int num = number;
System.Text.ASCIIEncoding asciiEncoding = new System.Text.ASCIIEncoding();
byte[] btNumber = new byte[] { (byte)num };
return asciiEncoding.GetString(btNumber);
}/* 何问起 hovertree.com */
}
}
生成文件夹和里面的图片
2.c++ opencv 处理生成最终的训练数据集
依据上一步生成的图片,字体大小不一,宽高比也不相同,而数据的要求是输入图像归一处理为相同的尺寸,且宽=高,同时为了训练的有效,需要尽可能确保训练数据集接近实际测试的数据集,所以还要对生成的图片完成字体区域的提取,变形,扭曲,字体颜色与背景颜色的随机修改,代码如下:
/*********************************************************
生成用于训练的单字符标准数据集合,10个数字,26个字母,7种字体类型
6个字母在一张图上,图片总数为:(36/6)*7*(255/25)*(255/5)
每张图片在生成是二值化阈值随机生成,实现字符边缘随机侵蚀,
背景色和 字体色会遍历选取,确保样本多样性。同时生成标注文件.txt
*/
#include"OPENCV.h"
Mat yuans, ymyy;
ostringstream ossw;//连接存储标注文件的内容(.txt)
ostringstream oss;
ostringstream ossSaveName;
ostringstream ossN;
void getPicAng(Mat& src);///对字体进行小量的随机变形,使用四点透视变形的算法 完成
Mat getcharRect(Mat Img);//从灰度图中获取字体的区域
void getRandomMat(Mat& ImgIn, Mat& ImgOut, int backC, int charC);///对字符区域的前后景随机渲染
int main(int, char** argv)
{
string savePath = "D://ziliao//mycharkind//";//文件保存的地址
int rectW = 100;//最终字符的高
int rectH = 55;//最终字符的宽
for (int M = 0; M < 37; M++) ///遍历每个字符的37个字体种类
{
//Mat charM5[36];
for (int N = 0; N < 36; N++) ///遍历36个字符分类
{
oss << "D://Charimage//" << N;
oss << "/*.jpg"; //图片名字后缀,oss可以结合数字与字符串
string pattern = oss.str(); //oss.str()输出oss字符串,并且赋给pattern
oss.str("");
vector<Mat> input_images;
vector<String> input_images_name;
glob(pattern, input_images_name, false);
int all_num = input_images_name.size();
//------------------------------------------//
Mat charMs = imread(input_images_name[M]);///存储6张字符原图
for (int v = 255; v > 0; v = v - 30)遍历选择图像的背景色
{
int chC = 10;
for (int f = 255; f > 0; f = f - chC)遍历选择图像的字体颜色
{
Mat charM = charMs.clone();
if (abs(v - f) < 30) {
chC = 15;
}
else {
chC = 30;
}
if (abs(v - f) > 20) {//背景色与字体颜色中值差必须大于5,防止颜色太过接近影响训练
-----------------------------------
rectH = 100
rectW = 55;
Mat d_Img = getcoloMat( rectW, rectH, 0);
string labTxt = "";
vector<int> sxli;
vector<int> syli;
cvtColor(charM, charM, COLOR_BGR2GRAY);
threshold(charM, charM, 0, 255, THRESH_BINARY);//图像二值化
charM = getcharRect(charM);
getPicAng(charM);
int charH = rectH * ((double)random(70, 85) / (double)100);
int chrectW= rectW * ((double)random(70, 85) / (double)100);
int cW = (charM.cols * charH) / charM.rows;//依据实际的图像特征调整字符的大小和长宽比
cW = cW <= chrectW ? cW : chrectW;
resize(charM, charM, Size(cW, charH));
int xChar= (rectW- cW)* ((double)random(50, 100) / (double)100);
int yChar = (rectH - charH) * ((double)random(50, 100) / (double)100);
charM.copyTo(d_Img(Rect(xChar, yChar, cW,charH )));
///-------------------------------------------//
///随机处理含有6个字符的图片并保存
Mat optImg;
getRandomMat(d_Img, optImg, v, f);///随机处理
//medianBlur(optImg, optImg, 5);//中值模糊
GaussianBlur(optImg, optImg, Size(7, 7), 5, 5);//高斯模糊
int id = N < 24 ? N : N - 1;
if (N!=24) {
ossSaveName << savePath << id << "//Char_" << M << N << v << f;
string strImg = ossSaveName.str();
ossSaveName.str("");
imwrite(strImg + ".jpg", optImg);
imshow("ss", optImg);
d_Img = getcoloMat(rectH, rectW, 0);
labTxt = "";
waitKey(1);
}
}
}
}
}
}
}
/// <summary>
/// 对字体进行小量的随机变形,使用四点透视变形的算法 完成
/// </summary>
/// <param name="ImgIn">输入图像</param>
/// <param name="ImgOut">输出图像</param>
/// <param name="backC">背景灰度值的中值</param>
/// <param name="charC">字体灰度值的中值</param>
void getRandomMat(Mat& ImgIn, Mat& ImgOut, int backC, int charC)
{
int colorbW = random(5, 15);///随机生成背景色或者前景色的分布宽度的1/2
int colorfW = random(3, 10);///随机生成背景色或者前景色的分布宽度的1/2
Mat d_Img = ImgIn.clone();
const int channels = d_Img.channels();
int nRows = d_Img.rows;
int nCols = d_Img.cols * channels;
//用指针访问像素,速度最快
uchar* p;
for (int i = 0; i < nRows; i++)
{
p = d_Img.ptr<uchar>(i);//获取每行首地址
for (int j = 0; j < nCols; ++j)
{
if (p[j] > 100)
{
int Max = (charC + colorfW);
int Min = charC - colorfW;
Max = Max < 255 ? Max : 255;
Min = Min > 0 ? Min : 0;
int kp = random(Min, Max);
p[j] = kp;
}
else {
int Max = backC + colorbW;
int Min = backC - colorbW;
Max = Max < 255 ? Max : 255;
Min = Min > 0 ? Min : 0;
int kp = random(Min, Max);
p[j] = kp;
}
}
}
ImgOut = d_Img;
}
/// <summary>
/// 查找灰度图中的第一个轮廓区域
/// </summary>
/// <param name="Img">目标图像</param>
/// <returns>返回查找到的第一个轮廓区域</returns>
Mat getcharRect(Mat Img)
{
Mat threshold_output = Img.clone();
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(threshold_output, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));
vector<vector<Point> > contours_poly(contours.size());
vector<Rect> boundRect(contours.size());
vector<Point2f>center(contours.size());
vector<float>radius(contours.size());
for (size_t i = 0; i < contours.size(); i++)
{
approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);
boundRect[i] = boundingRect((Mat)contours[i]);
minEnclosingCircle(contours_poly[i], center[i], radius[i]);
}
Mat reM;
Img(boundRect[0]).copyTo(reM);
return reM;
}
/// <summary>
/// 随机透视变化图像。实现字符扭曲
/// </summary>
/// <param name="src">处理图像</param>
void getPicAng(Mat& src)
{
Mat dst_warp, dst_warpRotateScale, dst_warpTransformation, dst_warpFlip;
Point2f srcPoints[4];//原图中的四点 ,一个包含三维点(x,y)的数组,其中x、y是浮点型数
Point2f dstPoints[4];//目标图中的四点
///完整选择图像原图
srcPoints[0] = Point2f(0, 0);
srcPoints[1] = Point2f(0, src.rows);
srcPoints[2] = Point2f(src.cols, 0);
srcPoints[3] = Point2f(src.cols, src.rows);
(double)random(0, 10) / (double)100;
int ap1 = 5;
int ap2 = 95;
//映射后的四个坐标值
dstPoints[0] = Point2f(src.cols * ((double)random(0, ap1) / (double)100), src.rows * ((double)random(0, ap1) / (double)100));
dstPoints[1] = Point2f(0 + src.cols * ((double)random(0, ap1) / (double)100), src.rows * ((double)random(ap2, 100) / (double)100));
dstPoints[2] = Point2f(src.cols * ((double)random(ap2, 100) / (double)100), 0 + src.rows * ((double)random(0, ap1) / (double)100));
dstPoints[3] = Point2f(src.cols * ((double)random(ap2, 100) / (double)100), src.rows * ((double)random(ap2, 100) / (double)100));
Mat M1 = getPerspectiveTransform(srcPoints, dstPoints);//由四个点对计算透视变换矩阵
warpPerspective(src, dst_warp, M1, src.size());//仿射变换
src = dst_warp;
}
生成 结果: