一、目标识别技术概述
1、摘要
目标检测是计算机视觉中最基本和最具挑战性的问题之一,它试图从自然图像中的大量预定义类别中定位目标实例。深度学习技术已成为直接从数据中学习特征表示的强大策略,并在通用目标检测领域取得了显著突破。鉴于这一快速发展时期,本文的目标是全面综述深度学习技术在这一领域的最新成就。这项调查包括300多项研究贡献,涵盖了通用对象检测的许多方面:检测框架、对象特征表示、对象提议生成、上下文建模、训练策略和评估指标。我们通过确定未来研究的前景来完成调查。
2、介绍
作为计算机视觉中的一个长期、基本和具有挑战性的问题,目标检测(如图1所示)几十年来一直是一个活跃的研究领域(Fischler和Elschlager 1973)。目标检测的目标是确定图像中是否存在来自给定类别(如人类、汽车、自行车、狗或猫)的任何对象实例,如果存在,则返回每个对象实例的空间位置和范围(例如,通过边界框Everingham等人2010;Russakovsky等人2015)。作为图像理解和计算机视觉的基石,目标检测是解决复杂或高级视觉任务的基础,例如分割、场景理解、目标跟踪、图像字幕、事件检测和活动识别。目标检测支持广泛的应用,包括机器人视觉、消费电子、安全、自动驾驶、人机交互、基于内容的图像检索、智能视频监控和增强现实。
最近,深度学习技术(Hinton和Salakhutdinov 2006;LeCun等人,2015)已成为从数据中自动学习特征表示的强大方法。特别是,如图3所示,这些技术在对象检测方面提供了重大改进。
如图2所示,物体检测可以分为两种类型之一(Grauman和Leibe 2011;Zhang等人,2013):特定实例的检测与广泛类别的检测。第一种类型旨在检测特定对象的实例(例如唐纳德·特朗普的脸、埃菲尔铁塔或邻居的狗),本质上是一个匹配问题。第二种类型的目标是检测某些预定义对象类别(例如人、汽车、自行车和狗)的实例(通常以前未看到)。历史上,目标检测领域的大部分工作都集中在单个类别(通常是人脸和行人)或几个特定类别的检测上。相比之下,在过去几年中,研究界已开始朝着更具挑战性的目标迈进,即建立通用目标检测系统,在该系统中,目标检测能力的广度与人类相当。
Krizhevsky等人(2012a)提出了一种称为AlexNet的深度卷积神经网络(DCNN),该网络在大规模视觉识别挑战(ILSVRC)中实现了破纪录的图像分类精度(Russakovsky等人,2015)。自那时以来,计算机视觉的大多数方面的研究重点都是深度学习方法,实际上包括通用对象检测领域(Girshick等人2014;He等人2014;Girshick 2015;Sermanet al.2014;Ren等人2017)。虽然已经取得了巨大的进步,如图3所示,但我们不知道在过去5年中对这一主题进行了全面的调查。鉴于进展速度异常迅速,本文试图跟踪最新进展并总结其成果,以便更清楚地了解通用目标检测中的当前全景。
3、与之前算法的比较
如表1所示,已经发布了许多著名的物体检测调查。其中包括许多关于特定物体检测问题的优秀调查,例如行人检测(Enzweiler和Gavrila 2009;Geronimo等人2010;Dollar等人2012)、人脸检测(Yang等人2002;Zafeiriou等人2015)、车辆检测(Sun等人2006)和文本检测(Ye和Doermann 2015)。除了张等人(2013)就对象类检测主题进行的调查外,最近直接关注一般对象检测问题的调查相对较少。然而,在Grauman和Leibe(2011)、Andreopoulos和Tsotsos(2013)以及Zhang等人(2013)中回顾的研究大多是在2012年之前,因此在深度学习和相关方法最近取得显著成功并占据主导地位之前。
深度学习允许计算模型学习极其复杂、微妙和抽象的表示,推动了广泛问题的重大进展,如视觉识别、物体检测、语音识别、自然语言处理、医学图像分析、药物发现和基因组学。在不同类型的深度神经网络中,DCNN(LeCun等人,1998、2015;Krizhevsky等人,2012a)在处理图像、视频、语音和音频方面取得了突破。可以肯定的是,已经发表了许多关于深度学习的调查,包括Bengio等人(2013)、LeCun等人(2015)、Litjens等人(2017)、Gu等人(2018),以及最近在ICCV和CVPR的教程中发表的调查。
相比之下,虽然已经提出了许多基于深度学习的目标检测方法,但我们不知道最近有任何全面的调查。全面回顾和总结现有工作对于目标检测的进一步进展至关重要,特别是对于希望进入该领域的研究人员而言。由于我们的重点是一般物体检测,因此将不考虑针对特定物体检测的DCNN的广泛工作,例如人脸检测(李等人2015a;张等人2016a;胡等人2017)、行人检测(张等人2016b;何桑等人2015)、车辆检测(周等人2016b)和交通标志检测(朱等人2016b)。
4、范围
基于深度学习的通用目标检测的论文数量惊人。事实上,有如此之多的内容,以至于对最新技术的任何全面综述都超出了任何合理篇幅的论文的范围。因此,有必要制定选择标准,将我们的重点限制在顶级期刊和会议论文上。由于这些局限性,我们真诚地向那些作品未包含在本文中的作者道歉。有关相关主题工作的调查,读者请参阅表1中的文章。本综述主要关注过去5年的主要进展,我们将注意力限制在静态图片上,将视频对象检测这一重要课题作为未来单独考虑的主题。
二、OpenCV级联分类器(CascadeClassifier)生成方法
OpenCV级联分类器数据(比如:cascade.xml)用于实现目标识别的核心数据。
CascadeClassifier则是opencv下objdetect模块中用来做目标检测的级联分类器的一个类;简而言之是滑动窗口机制+级联分类器的方式;早期opencv版本仅支持haar特征的目标检测,分别在opencv2.2和2.4之后开始支持LBP和HOG特征的目标检测。
使用现成的级联分类器数据局限性太多了,工业软件都是自己制作。
1、级联分类器数据的制作流程
2、图片准备与预处理
(1)图片最好用现场拍摄的照片,尽量不要过多的处理,原本最好!
(2)文件夹、文件名等,最好不要有汉字等,数字与字母最好;文件夹最好不要有空格!
(3)本文的《层级分类器集成生成环境》可以帮助你一键生成同样大小、黑白以及按数字命名的正样本图片及负样本图片。
3、正样本 Positive Images(Samples)
正样本图片,是告诉程序,这些 或 接近的就是目标!一般来说,**数量至少在 1000 以上。**本文只是示意性的,仅仅给出几十个图片,这是不够的哈。
(1)正样本 一般要求 20x20,24x24,64x64;
(2)现在有了本文的《层级分类器集成生成环境》,你随便,正样本只要截取的是正方形(其实也不一定)的即可。
(3)彩色?黑白?无所谓。
(4)正样本文件 pos.txt 是为了生成 正样本矢量化数据 pos.vec 准备的;
posdata/001.jpg 1 0 0 20 20
posdata/002.jpg 1 0 0 20 20
posdata/003.jpg 1 0 0 20 20
posdata/004.jpg 1 0 0 20 20
posdata/005.jpg 1 0 0 20 20
posdata/006.jpg 1 0 0 20 20
posdata/007.jpg 1 0 0 20 20
posdata/008.jpg 1 0 0 20 20
posdata/009.jpg 1 0 0 20 20
posdata/010.jpg 1 0 0 20 20
posdata/011.jpg 1 0 0 20 20
posdata/012.jpg 1 0 0 20 20
正样本文件 pos.txt 每行的格式是:
文件名 样本目标数 起点x 起点 y 宽度 高度
文件名可以用相对路径。样本数建议为1。起点建议为(0,0)。
本文的《层级分类器集成生成环境》可以帮助你一键生成 pos.txt 文件。图片尺寸自动提取。
4、正样本 矢量化
正样本矢量化数据 pos.vec 是训练必须的基础数据,是归一化、标准化、矢量化的 正样本数据,可以不再使用正样本图片及其数据,因而大大提高训练效率。
正样本矢量化数据 pos.vec 用 opencv_createsamples.exe 生成。
Windows cmd:
opencv_createsamples.exe -vec pos.vec -info pos.txt -num 12 -w 20 -h 20
Windows PowerShell:
.\opencv_createsamples.exe -vec pos.vec -info pos.txt -num 12 -w 20 -h 20
参数简要说明:
-vec pos.vec 生成的矢量文件
-info pos.txt 正样本文件
-num 12 正样本数量
-w 20 正样本尺寸(宽)
-h 20 正样本尺寸(高)
本文的《层级分类器集成生成环境》可以帮助你一键生成 pos.vec 文件。不需要进入 cmd 或 PowerShell 去敲键盘!
5、负样本 Negative Images(Samples)
(1)负样本 是告诉识别程序,哪些元素是目标中没有的!数量至少是正样本的 3 倍。不好凑!可以多复制几份哈。可以凑合的。
(2)负样本,大小无所谓;唯一的要求是不能含有正样本的物体;建议尺寸为 320x240 -- 640x480 ;
(3)负样本文件 neg.txt
C:/Downloads/Baidu/opencv_bin/negdata/001.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/002.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/003.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/004.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/005.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/006.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/007.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/008.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/009.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/010.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/011.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/012.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/013.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/014.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/015.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/016.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/017.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/018.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/019.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/020.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/021.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/022.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/023.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/024.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/025.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/026.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/027.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/028.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/029.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/030.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/031.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/032.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/033.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/034.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/035.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/036.jpeg
C:/Downloads/Baidu/opencv_bin/negdata/037.jpeg
负样本文件,只需要指定用于训练的 负样本图片即可。绝对地址!绝对地址!绝对地址!
本文的《层级分类器集成生成环境》可以帮助你一键生成 neg.txt 文件。
正样本数 :负样本数 至少是 1:3
6、训练 Cascade Trainning
有了 pos.vec 与 neg.txt 文件,就可以进行训练了。这个过程 坑 比较多。
Windows cmd:
opencv_traincascaded.exe -data xml -vec pos.vec -bg neg.txt -numPos 12 -numNeg 37 -numStages 20 -w 20 -h 20 -maxFalseAlarmRate 0.5 -mode ALL
Windows PowerShell:
.\opencv_traincascaded.exe -data xml -vec pos.vec -bg neg.txt -numPos 12 -numNeg 37 -numStages 20 -w 20 -h 20 -maxFalseAlarmRate 0.5 -mode ALL
注意几点:
(1)先检查 pos.vec ,neg.txt 文件是否存在?是否符合要求?
(2)建议创建子目录 posdata 放置 正样本 图片;子目录 negdata 放置 负样本图片;
(3)必须创建 xml 子目录;生成结果就在这个子目录下;
(4)无需生成 neg.vec
这个过程很吃资源,很慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢慢,要花很长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长长的时间。
敲击这些参数,很麻烦的。经过漫长的等待,最终生成 xml/cascade.xml 文件,大功告成!
本文的《层级分类器集成生成环境》可以帮助你一键做好上述工作,自动完成训练,生成 cascade.xml 文件!!!!!
三、《层级分类器集成生成环境》源代码
1、老老程序员的唠叨
自己制作 级联分类器数据 不是一件轻松、简单的事情,但没有级联分类器数据几乎无法开展后续的工作。因而不得不硬着头皮做下去。
看到大多数朋友都喜欢进入 cmd 去 key key key,效率实在是太低了。
我们这些从 DOS 过来的骨灰级程序员,有了 Windows 或 iOS 的 GUI ,终于摆脱了 KEY KEY KEY ,看到没有 GUI 的程序简直是受罪。关键是效率太低了。3天的活,本来1小时可以搞掂。
为了提高效率,很多年前就写了 《层级分类器集成生成环境》,现在按 Visual Studio 2022 做些改动与改进,放出来分享。
代码并不完善,有很多不足,请自行修改完善之。
2、准备工作
首先需要有 openCV 训练器相关程序:
(1)使用 openCV 开源,自己或使用别人编译好的 build ;
(2)去 OpenCV 官网下载:
OpenCV 官网下载https://opencv.org/releases/
(3)(多动症儿童〄的博客_CSDN博客-百度AI,笔记领域博主)提供的下载:
反正,你需要准备下面这些文件,并且复制到 Visual Studio 项目文件的 bin/debug/opencv_bin 目录下。
opencv_annotation.exe
opencv_annotationd.exe
opencv_aruco341.dll
opencv_aruco341d.dll
opencv_bgsegm341.dll
opencv_bgsegm341d.dll
opencv_bioinspired341.dll
opencv_bioinspired341d.dll
opencv_calib3d341.dll
opencv_calib3d341d.dll
opencv_ccalib341.dll
opencv_ccalib341d.dll
opencv_core341.dll
opencv_core341d.dll
opencv_createsamples.exe
opencv_createsamplesd.exe
opencv_datasets341.dll
opencv_datasets341d.dll
opencv_dnn341.dll
opencv_dnn341d.dll
opencv_dnn_objdetect341.dll
opencv_dnn_objdetect341d.dll
opencv_dpm341.dll
opencv_dpm341d.dll
opencv_face341.dll
opencv_face341d.dll
opencv_features2d341.dll
opencv_features2d341d.dll
opencv_ffmpeg341_64.dll
opencv_flann341.dll
opencv_flann341d.dll
opencv_fuzzy341.dll
opencv_fuzzy341d.dll
opencv_hfs341.dll
opencv_hfs341d.dll
opencv_highgui341.dll
opencv_highgui341d.dll
opencv_img_hash341.dll
opencv_img_hash341d.dll
opencv_imgcodecs341.dll
opencv_imgcodecs341d.dll
opencv_imgproc341.dll
opencv_imgproc341d.dll
opencv_interactive-calibration.exe
opencv_interactive-calibrationd.exe
opencv_line_descriptor341.dll
opencv_line_descriptor341d.dll
opencv_ml341.dll
opencv_ml341d.dll
opencv_objdetect341.dll
opencv_objdetect341d.dll
opencv_optflow341.dll
opencv_optflow341d.dll
opencv_phase_unwrapping341.dll
opencv_phase_unwrapping341d.dll
opencv_photo341.dll
opencv_photo341d.dll
opencv_plot341.dll
opencv_plot341d.dll
opencv_reg341.dll
opencv_reg341d.dll
opencv_rgbd341.dll
opencv_rgbd341d.dll
opencv_saliency341.dll
opencv_saliency341d.dll
opencv_shape341.dll
opencv_shape341d.dll
opencv_stereo341.dll
opencv_stereo341d.dll
opencv_stitching341.dll
opencv_stitching341d.dll
opencv_structured_light341.dll
opencv_structured_light341d.dll
opencv_superres341.dll
opencv_superres341d.dll
opencv_surface_matching341.dll
opencv_surface_matching341d.dll
opencv_text341.dll
opencv_text341d.dll
opencv_tracking341.dll
opencv_tracking341d.dll
opencv_traincascade.exe
opencv_version.exe
opencv_versiond.exe
opencv_video341.dll
opencv_video341d.dll
opencv_videoio341.dll
opencv_videoio341d.dll
opencv_videostab341.dll
opencv_videostab341d.dll
opencv_visualisation.exe
opencv_visualisationd.exe
opencv_waldboost_detector.exe
opencv_waldboost_detectord.exe
opencv_xfeatures2d341.dll
opencv_xfeatures2d341d.dll
opencv_ximgproc341.dll
opencv_ximgproc341d.dll
opencv_xobjdetect341.dll
opencv_xobjdetect341d.dll
opencv_xphoto341.dll
opencv_xphoto341d.dll
特别注意,有些下载得到的文件
opencv_traincascaded.exe
而不是:
opencv_traincascade.exe
请修改为:opencv_traincascade.exe
CVTrainer项目的目录结构是:
3、图片预处理源代码
功能包括:批量处理;统一尺寸;统一名称;统一后缀;
cs
#region 图片批量转换
/// <summary>
/// 原始文件夹
/// </summary>
private string FromFolder { get; set; } = "";
/// <summary>
/// 目标文件夹
/// </summary>
private string SaveFolder { get; set; } = "";
/// <summary>
/// 图片尺寸信息
/// </summary>
private int[] SaveSizes { get; set; }
/// <summary>
/// 数字型文件名的序列号
/// </summary>
private int SaveIndex { get; set; } = 1;
private void tpNormalize_SizeChanged(object sender, EventArgs e)
{
txtFrom.Width = tpNormalize.Width - txtFrom.Left - btnFrom.Width - 60;
btnFrom.Left = txtFrom.Left + txtFrom.Width + 5;
btnFrom.Top = txtFrom.Top;
btnFrom.Height = txtFrom.Height;
}
/// <summary>
/// 浏览原始文件夹
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnFrom_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
FromFolder = folderBrowserDialog1.SelectedPath;
txtFrom.Text = FromFolder;
SaveSizes = ImageSizes(FromFolder);
if (SaveSizes[0] != SaveSizes[1]) txtFromWidth.Text = SaveSizes[0] + "..." + SaveSizes[1];
else txtFromWidth.Text = SaveSizes[0] + "";
if (SaveSizes[2] != SaveSizes[3]) txtFromHeight.Text = SaveSizes[2] + "..." + SaveSizes[3];
else txtFromHeight.Text = SaveSizes[2] + "";
txtSaveWidth.Text = SaveSizes[0] + "";
txtSaveHeight.Text = SaveSizes[2] + "";
}
}
/// <summary>
/// 浏览并指定目标文件夹
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSave_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
SaveFolder = folderBrowserDialog1.SelectedPath;
txtSave.Text = SaveFolder;
}
}
/// <summary>
/// 图片的批量处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnNormalize_Click(object sender, EventArgs e)
{
if (FromFolder.Length < 1)
{
MessageBox.Show("browse the source folder.");
return;
}
if (SaveFolder.Length < 1)
{
MessageBox.Show("browse the destination folder.");
return;
}
int w = int.Parse("0" + txtSaveWidth.Text);
int h = int.Parse("0" + txtSaveHeight.Text);
try
{
int idx = 0;
SaveIndex = 1;
DirectoryInfo fd = new DirectoryInfo(FromFolder);
FileInfo[] xfiles = fd.GetFiles();
progressBar1.Maximum = xfiles.Count();
progressBar1.Visible = true;
foreach (FileInfo fx in xfiles)
{
progressBar1.Value = idx;
progressBar1.Refresh();
if (IsImageFile(fx.Extension))
{
string filename = Path.Combine(SaveFolder, fx.Name);
if (cbNumericName.Checked)
{
filename = Path.Combine(SaveFolder, GetNumericalName());
}
// 如果只是复制
if (w == SaveSizes[0] && h == SaveSizes[2] && txtSaveExt.Text.Length == 0)
{
File.Copy(fx.FullName, filename);
continue;
}
else
{
Image img = Image.FromFile(fx.FullName);
if (txtSaveExt.Text.Length > 0)
{
filename = Path.Combine(SaveFolder, fx.Name.Replace(fx.Extension, "") + txtSaveExt.Text);
}
if (w > 0 && h > 0)
{
Bitmap bmp = new Bitmap(w, h);
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(img, 0, 0, w, h);
if(cbGray.Checked)
{
ToGrayImage(ref bmp);
}
bmp.Save(filename);
}
else if (w > 0 && h == 0)
{
h = (int)(w * (double)img.Height / (double)img.Width);
Bitmap bmp = new Bitmap(w, h);
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(img, 0, 0, w, h);
if (cbGray.Checked)
{
ToGrayImage(ref bmp);
}
bmp.Save(filename);
}
else
{
w = (int)(h * (double)img.Width / (double)img.Height);
Bitmap bmp = new Bitmap(w, h);
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(img, 0, 0, w, h);
if (cbGray.Checked)
{
ToGrayImage(ref bmp);
}
bmp.Save(filename);
}
}
}
}
progressBar1.Visible = false;
MessageBox.Show("OK!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
/// <summary>
/// 计算下一个可使用的数字型图片文件名
/// </summary>
/// <returns></returns>
private string GetNumericalName()
{
while (true)
{
string sname = String.Format("{0:D5}", SaveIndex);
if (!File.Exists(Path.Combine(SaveFolder, sname)))
{
return sname;
}
SaveIndex++;
}
}
#endregion
4、正样本处理相关源代码(POWER BY 315SOFT.COM)
一键生成 pos.txt 文件;一键生成 pos.vec 文件。
cs
#region 正样本
private void tabPage1_SizeChanged(object sender, EventArgs e)
{
txtPosFolder.Width = tabPage1.Width - txtPosFolder.Left - btnPosFolder.Width - 60;
btnPosFolder.Left = txtPosFolder.Left + txtPosFolder.Width + 5;
btnPosFolder.Top = txtPosFolder.Top;
}
/// <summary>
/// 浏览并指定 正样本 文件夹
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnPosFolder_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
txtPosFolder.Text = folderBrowserDialog1.SelectedPath;
txtPosCount.Text = CountImageFile(txtPosFolder.Text) + "";
}
}
/// <summary>
/// 生成 pos.txt 文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnPosTxt_Click(object sender, EventArgs e)
{
if (txtPosFolder.Text.Length < 1)
{
MessageBox.Show("Please browse the folder contains positive images!");
return;
}
try
{
// 为复制图片做准备
string imgPosFolder = Path.Combine(WorkFolder, @"posdata");
if (cbPosCopy.Checked)
{
if (!Directory.Exists(imgPosFolder))
{
Directory.CreateDirectory(imgPosFolder);
}
}
StringBuilder sb = new StringBuilder();
DirectoryInfo fd = new DirectoryInfo(txtPosFolder.Text);
FileInfo[] xfiles = fd.GetFiles();
foreach (FileInfo fx in xfiles)
{
if (IsImageFile(fx.Extension))
{
// 将样本图片文件复制到工作文件夹/posdata
if (cbPosCopy.Checked)
{
string df = Path.Combine(imgPosFolder, fx.Name);
File.Copy(fx.FullName, df);
}
sb.Append("posdata/");
sb.Append(fx.Name + " ");
sb.Append(txtSampleCount.Text + " ");
sb.Append(txtOriginX.Text + " ");
sb.Append(txtOriginY.Text + " ");
// 自动获取图片尺寸
Image img = Image.FromFile(fx.FullName);
sb.Append(img.Width + " ");
sb.AppendLine(img.Height + "");
// 按最小的图片设置 vector 参数
if (txtSampleWidth.Text.Length == 0)
txtSampleWidth.Text = img.Width + "";
else if (Int32.Parse(txtSampleWidth.Text) > img.Width)
txtSampleWidth.Text = img.Width + "";
if (txtSampleHeight.Text.Length == 0)
txtSampleHeight.Text = img.Height + "";
else if (Int32.Parse(txtSampleHeight.Text) > img.Height)
txtSampleHeight.Text = img.Height + "";
}
}
if (!Directory.Exists(WorkFolder))
{
Directory.CreateDirectory(WorkFolder);
}
File.WriteAllText(Path.Combine(WorkFolder, @"pos.txt"), sb.ToString(), Encoding.Default);
webBrowser1.DocumentText = sb.ToString().Replace("\r\n", "<br>\r\n");
MessageBox.Show(Path.Combine(WorkFolder, @"pos.txt") + " OK!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
/// <summary>
/// 生成 pos.vec 矢量文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnPosVec_Click(object sender, EventArgs e)
{
string filename = Path.Combine(WorkFolder, @"pos.txt");
if (!File.Exists(filename))
{
MessageBox.Show("Should first create pos.txt !");
return;
}
Directory.SetCurrentDirectory(WorkFolder);
StringBuilder sb = new StringBuilder();
//sb.Append(WorkFolder + "\\");
//sb.Append("opencv_createsamples.exe ");
sb.Append("-vec pos.vec ");
sb.Append("-info pos.txt ");
sb.Append("-num " + txtPosCount.Text + " ");
sb.Append("-w " + txtSampleWidth.Text + " ");
sb.Append("-h " + txtSampleHeight.Text);
Process p = new Process();
p.StartInfo.FileName = Path.Combine(WorkFolder, @"opencv_createsamples.exe");
p.StartInfo.Arguments = sb.ToString();
//是否使用操作系统shell启动
p.StartInfo.UseShellExecute = false;
//接受来自调用程序的输入信息
p.StartInfo.RedirectStandardInput = false;
//由调用程序获取输出信息
p.StartInfo.RedirectStandardOutput = true;
//重定向标准错误输出
p.StartInfo.RedirectStandardError = true;
//不显示程序窗口
p.StartInfo.CreateNoWindow = true;
//启动程序
p.Start();
//p.StandardInput.AutoFlush = false;
string output = p.StandardOutput.ReadToEnd();
//等待程序执行完退出进程
p.WaitForExit();
p.Close();
webBrowser1.DocumentText = output.Replace("\n", "<br>\n");
string filenname = Path.Combine(WorkFolder, @"pos.vec");
if (File.Exists(filename))
{
MessageBox.Show(filename + " OK!");
}
}
#endregion
5、负样本处理相关源代码
一键生成 neg.txt 文件;
cs
#region 负样本
private void tabPage2_SizeChanged(object sender, EventArgs e)
{
txtNegFolder.Width = tabPage2.Width - txtNegFolder.Left - btnNegFolder.Width - 60;
btnNegFolder.Left = txtNegFolder.Left + txtNegFolder.Width + 5;
btnNegFolder.Top = txtNegFolder.Top;
}
/// <summary>
/// 浏览并指定 负样本 文件夹
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnNegFolder_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
txtNegFolder.Text = folderBrowserDialog1.SelectedPath;
txtNegCount.Text = CountImageFile(txtNegFolder.Text) + "";
}
}
/// <summary>
/// 生成负样本 neg.txt 文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnNegTxt_Click(object sender, EventArgs e)
{
string imgNegFolder = Path.Combine(WorkFolder, @"negdata");
StringBuilder sb = new StringBuilder();
DirectoryInfo fd = new DirectoryInfo(txtNegFolder.Text);
FileInfo[] xfiles = fd.GetFiles();
foreach (FileInfo fx in xfiles)
{
if (IsImageFile(fx.Extension))
{
// 将样本图片文件复制到工作文件夹/negdata
if (cbNegCopy.Checked)
{
string df = Path.Combine(imgNegFolder, fx.Name);
File.Copy(fx.FullName, df);
sb.AppendLine(Path.Combine(imgNegFolder, fx.Name).Replace("\\", "/"));
}
else
{
sb.AppendLine(fx.FullName.Replace("\\", "/"));
}
}
}
if (!Directory.Exists(WorkFolder))
{
Directory.CreateDirectory(WorkFolder);
}
File.WriteAllText(Path.Combine(WorkFolder, @"neg.txt"), sb.ToString(), Encoding.Default);
webBrowser1.DocumentText = sb.ToString().Replace("\r\n", "<br>\r\n");
MessageBox.Show(Path.Combine(WorkFolder, @"neg.txt") + " OK!");
}
#endregion
6、训练源代码
一键生成 xml/cascade.xml 文件;
cs
#region 训练
private void btnTrainning_Click(object sender, EventArgs e)
{
string filename = Path.Combine(WorkFolder, @"pos.vec");
if (!File.Exists(filename))
{
MessageBox.Show("Should first create pos.vec !");
return;
}
filename = Path.Combine(WorkFolder, @"neg.txt");
if (!File.Exists(filename))
{
MessageBox.Show("Should first create neg.txt !");
return;
}
Directory.SetCurrentDirectory(WorkFolder);
StringBuilder sb = new StringBuilder();
sb.Append("-data xml ");
sb.Append("-vec " + txtVecFile.Text + " ");
sb.Append("-bg " + txtNegFile.Text + " ");
sb.Append("-numPos " + txtNumPos.Text + " ");
sb.Append("-numNeg " + txtNumNeg.Text + " ");
sb.Append("-numStages 20 ");
sb.Append("-w " + txtPosWidth.Text + " ");
sb.Append("-h " + txtPosHeight.Text + " ");
sb.Append("-minHitRate " + txtHitRate.Text + " ");
sb.Append("-weightTrimRate " + txtTrimRate.Text + " ");
sb.Append("-maxFalseAlarmRate " + txtFalseAlarm.Text + " ");
sb.Append("-stagetype " + txtStage.Text + " ");
sb.Append("-boosttype " + cxBoost.SelectedItem.ToString() + " ");
sb.Append("-featuretype " + cxFeature.SelectedItem.ToString() + " ");
sb.Append("-mode " + cxMode.SelectedItem.ToString() + " ");
Process p = new Process();
p.StartInfo.FileName = Path.Combine(WorkFolder, @"opencv_traincascade.exe");
p.StartInfo.Arguments = sb.ToString();
//是否使用操作系统shell启动
p.StartInfo.UseShellExecute = false;
//接受来自调用程序的输入信息
p.StartInfo.RedirectStandardInput = false;
//由调用程序获取输出信息
p.StartInfo.RedirectStandardOutput = true;
//重定向标准错误输出
p.StartInfo.RedirectStandardError = true;
//不显示程序窗口
p.StartInfo.CreateNoWindow = true;
//启动程序
this.Cursor = Cursors.WaitCursor;
p.Start();
//p.StandardInput.AutoFlush = false;
string output = p.StandardOutput.ReadToEnd();
//等待程序执行完退出进程
p.WaitForExit();
p.Close();
this.Cursor = Cursors.Default;
webBrowser1.DocumentText = output.Replace("\n", "<br>\n");
string xmlname = Path.Combine(WorkFolder, @"xml", @"cascade.xml");
if (File.Exists(xmlname))
{
MessageBox.Show(xmlname + " OK!");
}
}
#endregion
7、基础函数
Form1.cs
cs
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Drawing;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Legalsoft.CVTrainer
{
/// <summary>
/// OpenCV 训练集继承生成环境
/// 北京联高软件开发有限公司
/// Beijing Legal Software Ltd.
/// </summary>
public partial class Form1 : Form
{
/// <summary>
/// 工作目录
/// 一般是 CVTrainer.exe 下的 opencv_bin
/// </summary>
private string WorkFolder { get; set; } = "";
public Form1()
{
InitializeComponent();
#region 正样本相关
btnPosFolder.Height = txtPosFolder.Height;
WorkFolder = Path.Combine(Application.StartupPath, "opencv_bin");
lbWorkFolderValue.Text = WorkFolder;
cbPosCopy.Checked = true;
cbPosCopy.Cursor = Cursors.Hand;
btnPosTxt.Cursor = Cursors.Hand;
btnPosVec.Cursor = Cursors.Hand;
#endregion
#region 负样本相关
label14.Text = WorkFolder;
cbNegCopy.Cursor = Cursors.Hand;
btnNegTxt.Cursor = Cursors.Hand;
#endregion
#region 训练相关
label17.Text = WorkFolder;
btnTrainning.Cursor = Cursors.Hand;
cxBoost.SelectedIndex = 0;
cxFeature.SelectedIndex = 0;
cxMode.SelectedIndex = 2;
#endregion
#region 图片预处理相关
cbGray.Cursor = Cursors.Hand;
txtSaveExt.Text = "";
cbNumericName.Cursor = Cursors.Hand;
btnNormalize.Cursor = Cursors.Hand;
txtFrom.ReadOnly = true;
txtFromWidth.ReadOnly = true;
txtFromHeight.ReadOnly = true;
btnFrom.Cursor = Cursors.Hand;
txtSave.ReadOnly = true;
btnSave.Cursor = Cursors.Hand;
progressBar1.Visible = false;
#endregion
tabPage1.Text = " Positive Images ";
tabPage2.Text = " Negative Images ";
tabPage3.Text = " Cascade Train ";
tpNormalize.Text = " Image Normalization ";
panel1.Dock = DockStyle.Top;
tabControl1.Dock = DockStyle.Fill;
panel2.Dock = DockStyle.Fill;
webBrowser1.Dock = DockStyle.Fill;
this.Text = "C#,OpenCV Object Detect Images Trainer ------BEIJING LEGAL SOFTWARE LTD.";
this.StartPosition = FormStartPosition.CenterScreen;
}
private void Form1_Load(object sender, EventArgs e)
{
Summary();
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
if (tabControl1.SelectedIndex != 0)
{
Summary();
}
}
/// <summary>
/// 计算一些概要信息
/// </summary>
private void Summary()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("<style>b { font-weight:bold;color:#AA0000; } </style>");
sb.AppendLine("Work folder: " + WorkFolder + "<br>");
string folder = WorkFolder;
if (!Directory.Exists(folder))
{
sb.AppendLine("!Create Work folder " + WorkFolder + "<br>");
MessageBox.Show("Work folder not exist!\nYou can do nothing!");
return;
}
folder = Path.Combine(WorkFolder, "xml");
sb.AppendLine("Parameters folder: " + folder + "<br>");
if (!Directory.Exists(folder))
{
sb.AppendLine("!Create parameter(xml) folder " + folder + "<br>");
Directory.CreateDirectory(folder);
}
folder = Path.Combine(WorkFolder, "posdata");
sb.AppendLine("Positive folder: " + folder + "<br>");
if (!Directory.Exists(folder))
{
sb.AppendLine("!Create Positive folder " + folder + "<br>");
Directory.CreateDirectory(folder);
}
else
{
int count = CountImageFile(folder);
if (count > 0)
{
sb.AppendLine("There have " + count + " images<br>");
txtPosCount.Text = count + "";
txtNumPos.Text = count + "";
txtPosFolder.Text = folder;
}
if (File.Exists(Path.Combine(WorkFolder, @"pos.txt")))
{
sb.AppendLine("There have <b>pos.txt</b><br>");
int[] sz = ImageSizes(Path.Combine(WorkFolder, @"posdata"));
txtSampleWidth.Text = sz[0] + "";
txtSampleHeight.Text = sz[2] + "";
txtPosWidth.Text = sz[0] + "";
txtPosHeight.Text = sz[2] + "";
sb.AppendLine("Image size: " + sz[0] + "," + sz[2] + "<br>");
}
else
{
sb.AppendLine("There have't <b>pos.txt</b><br>");
}
if (File.Exists(Path.Combine(WorkFolder, @"pos.vec")))
{
sb.AppendLine("There have <b>pos.vec</b><br>");
}
else
{
sb.AppendLine("There have <b>not pos.vec</b><br>");
}
cbPosCopy.Checked = (count == 0);
}
folder = Path.Combine(WorkFolder, "negdata");
sb.AppendLine("Negative folder: " + folder + "<br>");
if (!Directory.Exists(folder))
{
sb.AppendLine("!Create Negative folder " + folder + "<br>");
Directory.CreateDirectory(folder);
}
else
{
int count = CountImageFile(folder);
if (count > 0)
{
sb.AppendLine("There have " + count + " images<br>");
txtNegCount.Text = count + "";
txtNumNeg.Text = count + "";
txtNegFolder.Text = folder;
}
if (File.Exists(Path.Combine(WorkFolder, @"neg.txt")))
{
sb.AppendLine("There have <b>neg.txt</b><br>");
}
else
{
sb.AppendLine("There have <b>not neg.txt</b><br>");
}
cbNegCopy.Checked = (count == 0);
}
webBrowser1.DocumentText = sb.ToString();
}
//...
// 这里添加上面的其他代码
}
}
基础函数:
cs
#region 基础函数
/// <summary>
/// 文件是图片吗?
/// 按文件后缀判别;可自行添加;
/// </summary>
/// <param name="ext"></param>
/// <returns></returns>
private bool IsImageFile(string ext)
{
ext = ext.ToLower();
if (ext == ".bmp") return true;
if (ext == ".gif") return true;
if (ext == ".png") return true;
if (ext == ".jpg" || ext == ".jpeg") return true;
return false;
}
/// <summary>
/// 统计指定目录下的图片文件数量
/// </summary>
/// <param name="folder"></param>
/// <returns></returns>
private int CountImageFile(string folder)
{
DirectoryInfo fd = new DirectoryInfo(folder);
int count = 0;
FileInfo[] xfiles = fd.GetFiles();
foreach (FileInfo fx in xfiles)
{
if (IsImageFile(fx.Extension))
{
count++;
}
}
return count;
}
/// <summary>
/// 提取指定文件夹下图片的尺寸(最小、最大的高度、宽度)
/// </summary>
/// <param name="folder"></param>
/// <returns></returns>
private int[] ImageSizes(string folder)
{
int min_width = Int16.MaxValue;
int max_width = 0;
int min_height = Int16.MaxValue;
int max_height = 0;
DirectoryInfo fd = new DirectoryInfo(folder);
FileInfo[] xfiles = fd.GetFiles();
foreach (FileInfo fx in xfiles)
{
if (IsImageFile(fx.Extension))
{
Image img = Image.FromFile(fx.FullName);
if (img.Width > max_width) max_width = img.Width;
if (img.Width < min_width) min_width = img.Width;
if (img.Height > max_height) max_height = img.Height;
if (img.Height < min_height) min_height = img.Height;
}
}
return new int[] { min_width, max_width, min_height, max_height };
}
/// <summary>
/// 图片转为黑白的
/// 简单的代码,看得懂一些;效率低下;
/// 不过,图片不多的够用了。
/// </summary>
/// <param name="bmp"></param>
private void ToGrayImage(ref Bitmap bmp)
{
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
Color cc = bmp.GetPixel(x, y);
int cx = Math.Min(255, (int)(cc.R * 0.299 + cc.G * 0.587 + cc.B * 0.114));
bmp.SetPixel(x, y, Color.FromArgb(cx, cx, cx));
}
}
}
#endregion
8、界面相关代码(略)
以上代码经 验证 基本可用,享受编程的乐趣吧。