畢業設計-戶外場景行人檢測
時光飛逝,大學時光如白馬過隙,加上今年的疫情影響。。。畢業的一剎那真的感慨萬千,話不多說,開始記錄一下我的畢業設計。
我的畢業設計是由老師選題,語言自由選擇,我在網上搜索許久,發現行人檢測多以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;View Codeimport 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);//灰度化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()來大概估計下精確率
測試精度
訓練精度
完結撒花,大學生涯已經過去,希望工作以後還能繼續更新,生命不息,學習不止!