毕业设计-户外场景行人检测

  时光飞逝,大学时光如白马过隙,加上今年的疫情影响。。。毕业的一刹那真的感慨万千,话不多说,开始记录一下我的毕业设计。

  我的毕业设计是由老师选题,语言自由选择,我在网上搜索许久,发现行人检测多以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();
        
    }
    
    
}
View Code

 

  效果图大致这样

第二步就是要训练模型,这一步没啥好说的,相信大家也看到有类似的项目说要什么伽马归一化、灰度化、计算HOG特征梯度啊等等,看上去很头大其实在配置OpenCv后只是一句方法的事,所以大家不要害怕,都是纸老虎而已。在这一步既然是要训练模型,必然要进行数据集样本的导入,方法很多,可以采用给图片加分类标签导入亦或是全部导入再加标签,我采用的是第二种,大家不要局限住,就是将数据集样本放进Mat矩阵而已,for循环足以;接下来我说一下所谓的灰度化等各个步骤:

  1、灰度化

 Imgproc.cvtColor(src, src, Imgproc.COLOR_BGR2GRAY);//灰度化
View Code

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));
View Code

这里的核参数没有注释掉不代表需要全部都要用!!!我只用了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;  
         
     }
}
View Code

这里网上就有现成的,抄就完事了,也不难,自己写也没问题。

然后在通过调用这两个方法完成检测

         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("未检测到目标!绘制矩形失败!输出原文件!");
            }
    }
View Code

好了,大体就是这些了,图片需要一张张检测,可以用svm.predict()来大概估计下精确率

测试精度

 

 训练精度

 

 完结撒花,大学生涯已经过去,希望工作以后还能继续更新,生命不息,学习不止!

posted @ 2020-07-04 11:04  随风随笔  阅读(446)  评论(0编辑  收藏  举报