Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2016:opencv

Zpracování videa s OpenCV

  • Gábor Árva
  • xarvag00

Zadání

Využijte OpenCV knihovny pro vhodnou platformu rodiny ARM (např. Raspberry Pi). S pomocí těchto knihoven realizujte řetězec pro snímaní scény a vyhodnocovaní obrazu s funkcemi pro dohledový systém, jako jsou detekce pohybu a rozpoznávání lidské tváře. Při detekování pohybu uložte příslušné videosekvence.

Hardware

Raspberry PI 3 B

400

Projekt je založen na univerzální mikrokontrolér Raspberry PI 3 B of firmy Raspberry Pi Foundation. Je to nejnovejší model dostupný od února 2016. Deska obsahuje:

  • 4x 1.2GHz 64bit ARM Cortex-A53 CPU
  • 1GB RAM
  • 4 USB ports
  • 40 GPIO ports
  • Full HDMI port
  • Ethernet port
  • 802.11b/g/n
  • Bluetooth 4.1/LE
  • Camera interface (CSI)
  • Display interface (DSI)
  • VideoCore IV aj pro 3D grafiku

Kamerový modul Genius FaceCam 320

Specifikace:

  • maximální rozlíšení 640×480 pixel
  • nízka cena
  • jednoduchost

Firmware

Celý frimware byl vytvořen v Geany Programmer's Editor, co je základné programovací prostředí dostupné na operačný systém Raspbian. OpenCV knihovny jsou zkompilované a instalované pomoci CMake:https://cmake.org. Vývojový diagram celého firmware-u:

Použité knihovny v kódu:

  #include <opencv2/opencv.hpp>
  #include <sstream>
  #include <string>
  #include <stdio.h>
  #include <iostream> 
  #include <ctime>
  #include "detectObject.h"

Definované parametry pro inicializace kamery:

  #define fps 30
  #define frame_width 640
  #define frame_height 480
  
  using namespace std;
  using namespace cv;

První část kódu se obsahuje inicializace promenných, inicializace pro kameru a konstruktor pro psaní videa:

  int main(int argc, char *argv[])
  {
  /* Load cascades */
      loadCascades();
  /* Define points */	
      Point roi_tl, roi_br;
      Point facepos_tl, facepos_br;
      Point temp_tl, temp_br;			
  /* Define matrixes */
     cv::Mat cameraFeed;
     cv::Mat gray_img;
     cv::Mat prev_img;
     cv::Mat MD_img;
     cv::Mat ROI_img;
     cv::Mat holdROI_img;
  /* Boolean variables */
     bool video_saving = false;
     bool open_new_file = false;
     bool motion_detected = false;
     bool face_detected = false;
     bool holdROI = false;
     bool lookforface = false;
  /* Initialize camera */						
     cv::VideoCapture cap(0); 
     cap.set(CV_CAP_PROP_FRAME_WIDTH, frame_width);
     cap.set(CV_CAP_PROP_FRAME_HEIGHT, frame_height);
     cap.set(CV_CAP_PROP_FPS, fps);			
     cout << "Camera opened.." << endl;
  /* Constructor for video writing */	
     video_name = "Videos/Motion0.avi";
     VideoWriter oVideo(video_name, CV_FOURCC('M','J','P','G'),fps,Size(frame_width,frame_height),true);			
  /* Create windows
     cv::namedWindow("Camera Window",CV_WINDOW_AUTOSIZE);	
  

Druhý část je nekonečná smyčka v kterém jsou části pro snímání a zpracování jednotlivých obrazů. Protože algoritmus sleduje obličeje jenom v případe pohybu, dve další podmínky byly přidané do kódu jako lookforface a holdROI. Jestli v daném vybraném části ROI byl detekovaný obličej, část bude dále aktívní pro detekování dokud je obličej přítomen. S takým spůsobem systém je schopný získat blízké snímky o tváře i přes osoba nepohybuje.

  						
      for(;;)
      {							
          cap >> cameraFeed;				
          cvtColor(cameraFeed, gray_img, CV_BGR2GRAY);
          if(prev_img.empty() == true)
          {
               gray_img.copyTo(prev_img);		
          }	
          MD_img = motionDetect(gray_img, prev_img);	
          ROI_img = extractROI(MD_img, cameraFeed, motion_detected, roi_tl, roi_br);
          if( lookforface == true )
          {
               face_detected = lbp_faceDetect(ROI_img, roi_tl, facepos_tl, facepos_br);
               if(face_detected)
               {
                    temp_tl = roi_tl;
                    temp_br = roi_br;
                    holdROI = true;
               }
          }
          if( holdROI == true )
          {
               holdROI_img = cameraFeed(Rect(temp_tl, temp_br));
               holdROI = lbp_faceDetect(holdROI_img, temp_tl, facepos_tl, facepos_br);
               drawRectangle(cameraFeed, facepos_tl, facepos_br, Scalar(200,0,0), "FD");
               lookforface = false;	
          }
          else
          {
               lookforface = true;
          }
          if(motion_detected == true)
          {
               if(open_new_file)
               {
                     char name_buffer[50];
                     static char video_counter = 0;
                     string format = ".avi";
                     video_counter++;
                     sprintf(name_buffer, "Videos/Motion%d",video_counter);
                     video_name = name_buffer + format;
                     				 
                     oVideo.open(video_name, CV_FOURCC('M','J','P','G'),fps,Size(frame_width,frame_height),true);
                     cout << "saving video..." << endl;
                     open_new_file = false;
         }						
         }		
         if(saving)									
         {
               save_counter++;	
               writeDateTime(cameraFeed);	
               oVideo.write(cameraFeed);											
               if(save_counter > fps*5 )					
               {
                     oVideo.release();		
                     cout << "Saved to: " << video_name << endl;				
                     saving = false;
                     open_new_file = true;   
                     save_counter = 0;							
               }
        }	
        cv::imshow("Camera Window", cameraFeed);				
        if(cv::waitKey(10) >= 0) 
        {
            cout << "...exiting" << endl;
            break;
        }																						
   }
   return 0;

Základy funkcí motionDetect a faceDetect jsou převzaté z tutoriálů dostupné na oficiálne sktránke opencv: OpenCV tutorials a Cascade Classifiers

Principem funkce motionDetect je počítání absolutního rozdílu dvou obrazů. Na získání obraz se dál aplikuje threshold a další funkcí pro odstránení šumu. Referenční obraz prev_img je v každém smyčce míchané s aktuálním získaním obrazem cameraFeed.

  cv::Mat motionDetect(cv::Mat &curr_img,cv::Mat &prev_img)
  {
      double alpha = 0.15;
      double beta = 1.0 - alpha;
      cv::Mat threshold_img;
      
      absdiff(curr_img, prev_img, threshold_img);	
      /* binary threshold, s thresholdem 25, max. hodnota pixelu bude 255 (bílé) */
      threshold(threshold_img, threshold_img, 25, 255, 0); 	
      erode (threshold_img, threshold_img, Mat(), Point(-1,-1), 3);			
      dilate(threshold_img, threshold_img, Mat(), Point(-1,-1), 3);	
      /* míchání obrazů */
      addWeighted(curr_img, alpha, prev_img, beta, 0.0, prev_img );
      
      return threshold_img;
  }
  

Funkce extractROI se slúží pro vybíraní oblasti zájmu na základe výstupního obrazu funkce motionDetect. Nájde obrysy získaného obrazu, nakreslí kolem obdelník a vytahuje vybraný část z obrázku a to vrátí spolu s souřadnicemi roi_tl (top-left point) a roi_br (bottom-rigth point).

  cv::Mat extractROI(cv::Mat MD_img, cv::Mat &frame, bool &objectFound, Point &roi_tl, Point &roi_br)
  {	
      /* definice max. a min. povolená plocha kontúrov */
      const int MIN_BLOB_AREA = 50*50;
      const int MAX_BLOB_AREA = frame_width*frame_height/1.5;
      double refArea = 0;
      
      vector<vector<Point> > contours;
      vector<Vec4i> hierarchy;
      
      cv::Mat roi_img = Mat::zeros(10,10, CV_8UC3 ); 
      
      findContours(MD_img, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0,0));
      if(hierarchy.size() > 0 && hierarchy.size() < 50)
      {	
          vector<Rect> boundRect ( contours.size() );		
          for(size_t index = 0; index < contours.size(); index++ )
          {
              /* počítá obdelník kolem obrysu */
              boundRect[index] = boundingRect( Mat(contours[index]) );
              Moments moment = moments((cv::Mat)contours[index]);
              double area = moment.m00;		
              /* najít největší vyhovující blob */
              if(area > MIN_BLOB_AREA && area < MAX_BLOB_AREA & area > refArea )
              {
                  refArea = area;
                  roi_tl = boundRect[index].tl();
                  roi_br = boundRect[index].br();		
                  objectFound = true;						
              }
              else
              {
                  objectFound = false;		
              }		
          }	
          /* nakreslí obdelník kolem blobu */
          rectangle( frame, roi_tl, roi_br, Scalar(0,200,0), 2, 6, 0 );
          /* vytahuje část obrázku */
          roi_img = frame(Rect(roi_tl, roi_br));		
      }	
      return roi_img;	
  }

Nasledující funkce nakreslí obdelník na označení obrázek podla zadaných bodů. Dál je možní přímo určit farbu a krátky text který vloží do pravího dolného rohu.

  void drawRectangle(cv::Mat &frame, Point &p_tl, Point &p_br, Scalar color, std::string index)
  {
      Point org;
      org.x = p_br.x - 20;
      org.y = p_br.y - 5;
      rectangle( frame, p_tl, p_br, color, 2, 4 ,0 );
      putText( frame, index, org, 1, 1, color, 3, 6 );
  }

Funkce writeDateTime získá aktuální dátum a čas od systémové hodiny a do horné části vybraného obrazu.

  void writeDateTime(cv::Mat &frame)
  {
      time_t rawtime;
      struct tm *timeinfo;
      char buffer[80];
      /* získá dátum a čas */
      time(&rawtime);
      timeinfo = localtime(&rawtime);
      /konvertuje na string */
      strftime(buffer,80,"%d-%m-%Y %I:%M:%S",timeinfo);
      std::string str(buffer);
      /* přílepí dátum a čas do obrázku */ 
      Point org;
      org.x = 30;
      org.y = 30;
      putText(frame, str, org, 1, 1, Scalar(0,0,255), 1, 7 );															             
      /* červení farva, tlouštka 2, lineType = 7 */
  }      
  

Knihovna detectObject obsahuje funkce pro načítaní kaskádových klasifikátorů a detekce objektů. Nejprv jsou deklarované globálné parametry pro jednotlivých klasifikátorů, které lze nalézt v knihovnách OpenCV a jsou překopírované na vlastní adresář.

  String haar_face_cascade_name = "Cascades/haarcascade_frontalface_alt.xml";
  String eyes_cascade_name = "Cascades/haarcascade_eye.xml";
  String lbp_face_cascade_name = "Cascades/lbpcascade_frontalface.xml";
  
  CascadeClassifier lbp_face_cascade;
  CascadeClassifier haar_face_cascade;
  CascadeClassifier eyes_cascade;	
  int loadCascades(void)
  {
      /* načítaní */
      if( !haar_face_cascade.load(haar_face_cascade_name) ){ cout << "Error loading lbp_face cascade" << endl; return -1; }
      if( !lbp_face_cascade.load(lbp_face_cascade_name) ){ cout << "Error loading haar_face cascade" << endl; return -1; }
      if( !eyes_cascade.load(eyes_cascade_name) ){ cout << "Error loading eyes cascade" << endl; return -1; }	
  }

Funkce detectFace slouží pro detekce obličeje, v případě pozitivního výsledku vrátí souřadnice obličeje a vypisuje krátky text do terminálu:

  bool lbp_faceDetect( cv::Mat det_frame,cv::Point roi_pt, cv::Point &p_tl, cv::Point &p_br)
  {
      const int scale = 3;
      
      std::vector<Rect> faces
      cv::Mat frame_gray;
      
      cvtColor(det_frame, frame_gray, CV_BGR2GRAY);
      
      /* změna velikosti vstupného obrázku pro 3 krát měnší */
      cv::Mat resized_frame_gray( cvRound( frame_gray.rows / scale), cvRound(frame_gray.cols / scale), CV_8UC1);
      cv::resize( frame_gray, resized_frame_gray, resized_frame_gray.size() );
      
      //* skenování vstupního obrázku pro obličeje */
      lbp_face_cascade.detectMultiScale(resized_frame_gray, faces, 1.1, 2, 0, Size(20,20) );	
      for(size_t i = 0; i < faces.size(); i++)
      {
          /* získání souřadnice obličeje body //tl// a //br//.
          p_tl.x = roi_pt.x + faces[i].x*scale;
          p_tl.y = roi_pt.y + faces[i].y*scale;
          p_br.x = roi_pt.x + faces[i].x*scale + faces[i].width*scale;
          p_br.y = roi_pt.y + faces[i].y*scale + faces[i].height*scale;
      }
      if(faces.size() > 0)
      {		
          cout << "Face detected..." << endl;
          return true;
      }	
      return false;
  }
  

Video ukázka

Závěr

Zadání projektu bylo splnění v celém rozsahu. Program slouží jako část mojho diplomového práce, které budu dále rozvíjet aby detekoval aj více pohybujícich objektů. Dalším úkolem bude vytvořit vlastní klasifátor pro jiné části tela např. ruky.

V tomto stavu je program schopný detekovat nějvětší pohybující objekt a vtom najít obličeje.

2016/opencv.txt · Poslední úprava: 2017/01/10 13:56 autor: Gábor Árva