毕业设计-户外场景行人检测
时光飞逝,大学时光如白马过隙,加上今年的疫情影响。。。毕业的一刹那真的感慨万千,话不多说,开始记录一下我的毕业设计。
我的毕业设计是由老师选题,语言自由选择,我在网上搜索许久,发现行人检测多以Python+深度学习完成,本着推陈出新的精神我选择了Java+OpenCv(其实是pyhon不太熟悉哈哈)。当然,Java+OpenCv不知局限于行人检测,合适的数据集+核函数能应用于大多数的物 体检测,像车牌检测、超市商品检测诸如此类。
如果说采用Java+OpenCv的设计,第一步首先是OpenCv包的配置,这个很简单,各个网站(CSDN、博客园。。)都有详细说明,按照步骤即可,我配置的是OpenCv4..2.0,配置完后可以用下面的小demo测试一下
package com.opencv; import org.opencv.core.Core; import org.opencv.core.Mat; import org.opencv.core.MatOfDouble; import org.opencv.core.MatOfFloat; import org.opencv.core.MatOfRect; import org.opencv.core.Point; import org.opencv.core.Rect; import org.opencv.core.Scalar; import org.opencv.core.Size; import org.opencv.features2d.FastFeatureDetector; import org.opencv.features2d.Feature2D; import org.opencv.highgui.HighGui; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; import org.opencv.objdetect.HOGDescriptor; public class OpenCvMain { //静态代码块加载动态链接库 static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } public static void main(String[] args) { /* * IMREAD_UNCHANGED = -1 :不进行转化,比如保存为了16位的图片,读取出来仍然为16位。 * IMREAD_GRAYSCALE = 0 :进行转化为灰度图,比如保存为了16位的图片,读取出来为8位,类型为CV_8UC1。 * IMREAD_COLOR = 1 :进行转化为三通道图像。 * IMREAD_ANYDEPTH = 2 :如果图像深度为16位则读出为16位,32位则读出为32位,其余的转化为8位。 * IMREAD_ANYCOLOR = 4 :图像以任何可能的颜色格式读取 * IMREAD_LOAD_GDAL = 8 :使用GDAL驱动读取文件,GDAL(Geospatial Data Abstraction * Library)是一个在X/MIT许可协议下的开源栅格空间数据转换库。它利用抽象数据模型来表达所支持的各种文件格式。 * 它还有一系列命令行工具来进行数据转换和处理。 */ Mat src = Imgcodecs.imread("D:\\123.jpg");//待匹配图片,这里写你自己测试的图片地址 HighGui.imshow("原图", src); HighGui.waitKey(); Mat gary=new Mat(); //图片转灰 https://blog.csdn.net/ren365880/article/details/103869207 Imgproc.cvtColor(src, gary, Imgproc.COLOR_BGR2GRAY); /* * 使用默认参数创建HOG检测器。 * 默认值(Size(64,128),Size(16,16),Size(8,8),Size(8,8),9) */ HOGDescriptor hog=new HOGDescriptor(); /* * 设置线性SVM分类器的系数。 线性SVM分类器的 * @param svmdetector系数。 * HOGDescriptor.getDefaultPeopleDetector()返回经过训练可进行人员检测的分类器的系数(对于64x128窗口)。 */ hog.setSVMDetector(HOGDescriptor.getDefaultPeopleDetector()); MatOfRect rect=new MatOfRect(); /* * 检测输入图像中不同大小的对象。 检测到的对象将作为矩形列表返回。 * @param img类型CV_8U或CV_8UC3的矩阵,其中包含检测到对象的图像。 * @param foundLocations矩形的向量,其中每个矩形都包含检测到的对象。 * @param foundWeights向量,它将包含每个检测到的对象的置信度值。 * @param hitThreshold要素与SVM分类平面之间距离的阈值,通常为0,应在检测器系数中指定 * (作为最后一个自由系数),但是如果省略自由系数(允许),则可以指定 在这里手动操作。 * @param winStride窗口跨度。 它必须是跨步的倍数。 * @param padding填充 */ hog.detectMultiScale(gary, rect, new MatOfDouble(),0,new Size(8,8),new Size(0,0)); Rect[] rects = rect.toArray(); for (int i = 0; i < rects.length; i++) { /* * 绘制一个简单的,粗的或实心的直角矩形。 函数cv :: rectangle绘制一个矩形轮廓或一个填充的矩形,其两个相对角为pt1和pt2。 * @param img图片。 * @param pt1矩形的顶点。 * @param pt2与pt1相反的矩形的顶点。 * @param color矩形的颜色或亮度(灰度图像)。 * @param thickness组成矩形的线的粗细。 负值(如#FILLED)表示该函数必须绘制一个填充的矩形。 * @param lineType线的类型。 请参阅https://blog.csdn.net/ren365880/article/details/103952856 */ Imgproc.rectangle(src, new Point(rects[i].x,rects[i].y), new Point(rects[i].x+rects[i].width,rects[i].y+rects[i].height), new Scalar(0,0,255), 2, Imgproc.LINE_AA); } HighGui.imshow("行人检测", src); HighGui.waitKey(); } }
效果图大致这样
第二步就是要训练模型,这一步没啥好说的,相信大家也看到有类似的项目说要什么伽马归一化、灰度化、计算HOG特征梯度啊等等,看上去很头大其实在配置OpenCv后只是一句方法的事,所以大家不要害怕,都是纸老虎而已。在这一步既然是要训练模型,必然要进行数据集样本的导入,方法很多,可以采用给图片加分类标签导入亦或是全部导入再加标签,我采用的是第二种,大家不要局限住,就是将数据集样本放进Mat矩阵而已,for循环足以;接下来我说一下所谓的灰度化等各个步骤:
1、灰度化
Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY);//灰度化
src是保存数据样本的mat矩阵,也就是第一步存储数据集样本的矩阵。
2、归一化
Imgproc.resize(src, src, new Size(64,128));
src同上,如果感觉麻烦可以新建一个mat,将src保存到新mat,new Size()方法,请注意这里,务必与HOG描述子成一定比例,否则会报错
3、计算HOG梯度
HOGDescriptor hog = new HOGDescriptor(new Size(64 ,128), new Size(16, 16), new Size(8, 8), new Size(8, 8), 9);//调试
记住这里的提前的比例一定要与归一化的成比例,慎记。
hog.compute(src,descriptors);
OpenCv都包含各种方法,计算是一句话的事。
第三步就是要开始训练模型,这里记住核函数的选取和调参很重要,还有迭代次数及终止条件,
svm.setType(SVM.C_SVC);//SVM的类型,默认是:SVM.C_SVC向量回归机 svm.setKernel(SVM.RBF);//使用预先定义的内核初始化 svm.setGamma(0.5);//核函数的参数 svm.setCoef0(1.0);//核函数有关参数 svm.setC(0.01);//SVM优化问题的参数C svm.setP(0.1);//SVM优化问题的参数p EPS_SVR设置 svm.setNu(0.5);//SVM优化问题参数 svm.setTermCriteria(new TermCriteria(TermCriteria.EPS, nImgNum, 1e-5));
这里的核参数没有注释掉不代表需要全部都要用!!!我只用了Kernel和Gamma、setP,这里的选取和调参是老师帮助的,时间久远加上当时我也不太懂,所以我也不是很明白,这里的话建议问问有经验的人的意见,合适的核参数很重要。对于 svm.setTermCriteria()方法,这个是训练次数终止条件设置,
该类变量需要3个参数,一个是类型,第二个参数为迭代的最大次数,最后一个是特定的阈值 //setTermCriteria是用来设置算法的终止条件, SVM训练的过程就是一个通过 迭代 方式解决约束条件下的二次优化问题,这里我们指定一个最大迭代次数和容许误差,以允许算法在适当的条件下停止计算 .MAX_ITER迭代到最大迭代次数终止.EPS 迭代到阈值终止 .MAX_ITER+ TermCriteria.EPS 上述两者都作为迭代终止条件
可以看一下。然后将模型保存为.xml文件。
第四步就是检测阶段,这里就比较简单了,一个检测器加一个滑动窗口方法即可。
public Mat myDetector() { // TODO 自动生成的方法存根 System.loadLibrary(Core.NATIVE_LIBRARY_NAME); //SVM svm = SVM.load("E:\\BiShe\\svm_java\\SVM_HOG_2400PosINRIA.xml"); SVM svm = SVM.load("E:\\BiShe\\svm_java\\SVM_HOG_2400PosINRIA_12000Neg.xml"); Mat src = svm.getSupportVectors(); int numofsv = src.rows();//支持向量个数 //System.out.println("支持向量个数:"+numofsv); int svdim = svm.getVarCount();//特征向量维数,即HOG描述子的维数 //System.out.println("特征向量维数:"+svdim); //初始化alphamat和svindex Mat alphaMat = Mat.zeros(1, numofsv, CvType.CV_32FC1); Mat supportVectorMat = Mat.zeros(numofsv, svdim, CvType.CV_32FC1);//创建----32位无符号的单通道---灰度图片 Mat resultMat = Mat.zeros(1, svdim, CvType.CV_32FC1); Mat svidx = Mat.zeros(1, numofsv, CvType.CV_32FC1); //获得模型中的rho double rho = svm.getDecisionFunction(0, alphaMat, svidx); // System.out.println("rho:"+rho); //convertTo()函数负责转换数据类型不同的Mat,即可以将类似float型的Mat转换到imwrite()函数能够接受的类型。 alphaMat.convertTo(alphaMat, CvType.CV_32FC1); // System.out.println(alphaMat.rows()+","+alphaMat.cols()); //将支持向量和alpha复制到对应Mat中 supportVectorMat = src; Core.gemm(alphaMat, supportVectorMat, -1, new Mat(), 0, resultMat);//矩阵点乘? //定义一个大一维的向量,便于后面添加rho Mat myDetector = new Mat(1, svdim+1, CvType.CV_32FC1); for(int j=0;j<svdim;j++) { double[] value2 = resultMat.get(0, j); myDetector.put(0, j, value2[0]); } myDetector.put(0, svdim, rho); System.out.println("rho:"+myDetector.get(0, svdim)[0]); return myDetector; //开始检测 } /** * 判断两个矩形的重叠面积 * @param a * @param b * @return */ public int getOverLappingArea(Rect a,Rect b){ int overLappingArea = 0; int startX = Math.min(a.x,b.x); int endX = Math.max(a.x + a.width, b.x + b.width); int overLappingWidth = a.width + b.width - (endX - startX); int startY = Math.min(a.y, b.y); int endY = Math.max(a.y + a.height, b.y + b.height); int overLappingHeight = a.height + b.height - (endY - startY); if(overLappingWidth <= 0 || overLappingHeight <= 0) { overLappingArea = 0; }else { overLappingArea = overLappingWidth * overLappingHeight; } return overLappingArea; } }
这里网上就有现成的,抄就完事了,也不难,自己写也没问题。
然后在通过调用这两个方法完成检测
System.loadLibrary(Core.NATIVE_LIBRARY_NAME); System.load("E:\\BiShe\\opencv\\build\\java\\x64\\opencv_java420.dll"); System.out.println("类库加载成功"); MyDetector MD = new MyDetector(); Mat myDetector = new MyDetector().myDetector(); File dir1 = new File("E:\\BiShe\\NITCA_train\\part_test");//测试图片 File[] files1 = dir1.listFiles(); int nImgNum = files1.length; float sum ; HOGDescriptor hog = new HOGDescriptor(new Size(64,128), new Size(16, 16), new Size(8, 8), new Size(8, 8), 9); // hog.setSVMDetector(HOGDescriptor.getDefaultPeopleDetector()); hog.setSVMDetector(myDetector); //检测窗口(64,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9 Mat dataMat = null, resMat = null; System.out.println("行人检测"); dataMat=Imgcodecs.imread("E:\\BiShe\\INRIADATA\\original_images\\train\\pos\\crop001578.png"); Mat dst = new Mat(); MatOfDouble mod = new MatOfDouble(); hog.detectMultiScale(dataMat, mor, mod, 0, new Size(8, 8), new Size(32, 32), 1.05, 2); // 调用方法进行检测 //System.out.println("检测完毕!画出矩形..."); if(mor.toArray().length > 0){ //判断是否检测到目标对象,如果有就画矩形,没有就执行下一步 //找出所有没有嵌套的矩形框r,并放入found_filtered中,如果有嵌套的话,则取外面最大的那个矩形框放入found_filtered中 Rect[] found = mor.toArray(); List<Rect> found_filtered = new ArrayList<Rect>(); //先判断是否有嵌套 for(int m=0;m<found.length;m++) { Rect r = found[m]; int area = r.width*r.height; //System.out.println(r.x+","+r.y+";"+r.width+","+r.height); int n=0; for(;n<found.length;n++) { if(n!=m && MD.getOverLappingArea(r, found[n])==area)//且found[n]在r内 break; } if(n==found.length) { found_filtered.add(r); } } for(int j=0;j<found_filtered.size();j++){ Rect r = found_filtered.get(j); Imgproc.rectangle(dataMat, new Point(r.x, r.y), new Point(r.x + r.width, r.y + r.height),new Scalar(0, 0, 255), 2); } System.out.println("矩形绘制完毕!正在输出..."); HighGui.imshow("行人检测", dataMat); HighGui.waitKey(); }else{ System.out.println("未检测到目标!绘制矩形失败!输出原文件!"); } }
好了,大体就是这些了,图片需要一张张检测,可以用svm.predict()来大概估计下精确率
测试精度
训练精度
完结撒花,大学生涯已经过去,希望工作以后还能继续更新,生命不息,学习不止!