简介:大学生一枚,加入了学校项目组学习,参与到项目中,最近项目需要,通过各方面的学习,做了一个小小的物体识别的cascade.xml,把自己学到的干货分享出来。
环境:jdk1.8 opencv 3.4.0
首先,我们要有足量的图像,我相信这个各位是有办法收集到自己想要检测的目标的图像的。实际训练过程中,我发现500左右已经可以大体上识别到自己想要的目标,但是如果想要更高的精度,建议在1000+的正面例子,反面例子的话只要里面没有目标图像,可以是任何图像,但是建议根据实际需要,在所处的场景中截取不含正面例子的图像作为反面例子。据了解,正:反最佳比例是1:3左右哦。
下面开始上代码了,我把需要的东西都整合了,只需要图像文件地址就可以把所需的文件生成进行训练的操作。
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
public class Project{
//下面都是文件src,根据自己的实际情况填写
public static String posFilePath = "";//正面例子源文件夹
public static String posOutPath = "";//正面例子输出文件夹
public static String posTxtPath = "";//正面例子信息txt文件
public static String negFilePath = "";//反面例子源文件夹
public static String NegOutPath = "";//反面例子输出文件夹
public static String negTxtPath = "";//反面例子信息txt文件
//修改文件大小为20*20 可自定义设置,根据opencv官方推荐20*20效果最佳,
//个人建议最大不要超过40*40,太大的话跑不太动
public static int width = 20 ;
public static int height = 20 ;
//导入opencv库
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
public static void renameFiles(String filePath){
File file = new File(filePath);
File[] files = file.listFiles();
int i = 0;
for (File f : files){
f.renameTo(new File(filePath+"\\"+i+".jpg"));
i++;
}
}
//获取img和txt的过程
/*
* 1、对图片缩放至20*20
* 2、对图片进行灰度处理
* 3、获取Txt
* */
public static void getPosImgAndTxt() throws IOException {
File infile = new File(posFilePath);//获取正面例子的文件夹
File outfile = new File(posOutPath);//缩放和灰度处理后的例子存放文件夹
File txtFile = new File(posTxtPath);//txt信息文件的文件位置
if (txtFile.isDirectory()){
System.out.println("txt文件别忘了在文件夹地址后加上文件名");
return;
}
if (!txtFile.exists()){
txtFile.createNewFile();
System.out.println("文件不存在,自动创建完毕");
}
if (!outfile.exists()){
outfile.mkdir();
System.out.println("输出路径不存在,自动创建完毕");
}
if (!infile.isDirectory()){
System.out.println("请确保参数posFilePath为文件夹路径");
}else if (infile.isDirectory()){
File[] files = infile.listFiles();
FileOutputStream fileOutputStream = new FileOutputStream(txtFile);
PrintWriter printWriter = new PrintWriter(fileOutputStream,true);
for (File file :files){
Mat mat = Imgcodecs.imread(file.getPath());
//缩放
Imgproc.resize(mat,mat,new Size(width,height),0,0,Imgproc.INTER_CUBIC);
//灰度处理
Imgproc.cvtColor(mat,mat,Imgproc.COLOR_BGR2GRAY,0);
//获取图片
Imgcodecs.imwrite(outfile+"\\"+file.getName()+".jpg",mat);
//添加文件信息到txt文件中
printWriter.println(outfile.getPath()+"\\"+file.getName()+" 1 0 0 20 20");
}
printWriter.close();
fileOutputStream.close();
}
}
//获取反面img和txt的过程
/*
*1、对图片进行灰度处理
*2、获取txt
* */
public static void getNegImgAndTxt() throws IOException {
File infile = new File(negFilePath);//获取反面例子的文件夹
File outfile = new File(NegOutPath);//灰度处理后的例子存放文件夹
File txtFile = new File(negTxtPath);//txt信息文件的文件位置
if (txtFile.isDirectory()){
System.out.println("txt文件别忘了在文件夹地址后加上文件名");
return;
}
if (!txtFile.exists()){
txtFile.createNewFile();
System.out.println("文件不存在,自动创建完毕");
}
if (!outfile.exists()){
outfile.mkdir();
System.out.println("输出路径不存在,自动创建完毕");
}
if (!infile.isDirectory()){
System.out.println("请确保参数posFilePath为文件夹路径");
}else if (infile.isDirectory()){
File[] files = infile.listFiles();
FileOutputStream fileOutputStream = new FileOutputStream(txtFile);
PrintWriter printWriter = new PrintWriter(fileOutputStream,true);
for (File file :files){
Mat mat = Imgcodecs.imread(file.getPath());
//灰度处理
Imgproc.cvtColor(mat,mat,Imgproc.COLOR_BGR2GRAY,0);
//获取图片
Imgcodecs.imwrite(outfile+"\\"+file.getName()+".jpg",mat);
//添加文件信息到txt文件中
printWriter.println(outfile.getPath()+"\\"+file.getName());
}
printWriter.close();
fileOutputStream.close();
}
}
public static void main(String[] args) throws IOException {
/*
* 如果有需要,可以调用重命名函数,因为图像文件名中是
* 不可以有汉字的我的例子图像好多都是通过qq截图截的,其中含有“截图”这两个字,
* 所以被迫写一个重命名的函数,我把图像的名字都改成了数字
* 重命名函数已经写好,可以直接调用
* renameFiles(参数);
* 参数路径中不要有不是作为目标检测素材的其他文件,因为如果有非图像文件
* 需要的参数是文件夹路径,会将整个文件夹中的文件重命名为 数字.jpg 的格式
*
* */
getPosImgAndTxt();
getNegImgAndTxt();
}
}
经过上面的操作,我们的文件夹应该是这个样子的(见下图)
然后我们在这个文件夹里面创建一个名为xml的文件夹,用来保存训练所得的cascade.xml以及其他每一层的文件
然后我们要把opencv的一些文件复制过来进行应用路径为:
build/x64/vc14/bin,
内容如图,将其中的所有文件复制到我们需要训练模型的文件夹中。
如果没有这些文件可以在我的百度云里面下载:
链接:https://pan.baidu.com/s/115KfcgUr5SL_BmWJ6-73Cg
提取码:loiq
复制过去后我们的训练模型用的文件夹是这个样子的
之后,打开cmd进入到我们训练用文件夹输入:
opencv_createsamples.exe -vec pos.vec -info pos.txt -num 500 -w 20 -h 20
其中,-num 是指你的正面例子的个数, -w 是指我们的正面例子的宽度 ,-h 是指我们的正面例子的高度。
这段代码执行后,会生成文件pos.vec
然后在cmd中本文件夹目录下输入:
opencv_traincascade.exe -data xml -vec pos.vec -bg neg.txt -numPos 500 -numNeg 1500 -numStages 15 -w 20 -h 20 -minHitRate 0.999 -maxFalseAlarmRate 0.2 -weightTrimRate 0.95 -featureType LBP
其中-numStage是指训练层数,可以根据实际情况自己定,我自己跑的时候,由于跑到stage15~16附近就已经很慢了,所以我选择了15层,实际训练过程中发现样本数量越少跑起来越慢,在这段代码回车后会进行一段时间的模型训练在xml那个文件夹中生成一堆xml文件,用得到的是cascade.xml
上方参数详见官方帮助文档
这时候xml文件夹中是这样的:
然后就是对我们的模型进行应用啦代码如下:
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import java.io.File;
public class New {
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
CascadeClassifier carDetector = new CascadeClassifier("E:\\new2\\xml\\cascade.xml");//调取训练出来的模型
String filepath="E:\\project\\outputcam1";//输入路径,
String outPath="E:\\project\\success";//输出路径
File file=new File(filepath);
File outfile=new File(outPath);
if(!outfile.exists()) {
outfile.mkdir();
}if(!file.isDirectory()){
System.out.println("请输入正确的文件夹路径");
}else if(file.isDirectory()){
String[] fileList=file.list();
for(String imgName:fileList) {
Mat img = Imgcodecs.imread(filepath+"/"+imgName);
MatOfRect detections = new MatOfRect();
carDetector.detectMultiScale(img,detections);
int i = 0 ;
for (Rect rect :detections.toArray()){
//将识别到的目表物体框起来
Imgproc.rectangle(img,new Point(rect.x,rect.y),new Point(rect.x+rect.width,rect.y+rect.height),new Scalar(0,255,0));
//将框出来目标的图像输出
Imgcodecs.imwrite(outPath+"/"+imgName, img);
i ++;
}
}
}
}
}