Эксперименты с компьютерным зрением


Установка OpenCV

OpenCV (Open Computer Vision) – это открытая библиотека C++, которая содержит алгоритмы для: интерпретации изображений, калибровки камеры по эталону, устранения оптических искажений, определения сходства, распознавания жестов и т.д.

Установить OpenCV несложно (пример для ArchLinux):

[orca@blizzard ~]$ sudo pacman -S opencv opencv-samples

Для проверки собрать и запустить тестовую программу со следующим исходным кодом (файл helloworld.cpp):

#include <opencv/cv.h>
#include <opencv/highgui.h>

int main( int argc, char** argv )
{
  // Задать высоту и ширину картинки
  int height = 620;
  int width = 440;

  // Задать точку для вывода текста
  CvPoint pt = cvPoint( height/4, width/2 );

  // Создать 8-битную, 3-канальную картинку
  IplImage* hw = cvCreateImage(cvSize(height, width), 8, 3);
  // Залить картинку чёрным цветом
  cvSet(hw,cvScalar(0,0,0));

  // Инициализация шрифта
  CvFont font;
  cvInitFont( &font, CV_FONT_HERSHEY_COMPLEX,1.0, 1.0, 0, 1, CV_AA);
  // Используя шрифт вывести на картинку текст
  cvPutText(hw, "Testing OpenCV...", pt, &font, CV_RGB(150, 0, 150) );

  // Создать окно
  cvNamedWindow("Hello World", 0);
  // Показать картинку в созданном окне
  cvShowImage("Hello World", hw);
  // Ждать нажатия клавиши
  cvWaitKey(0);

  // Освободить ресурсы
  cvReleaseImage(&hw);
  cvDestroyWindow("Hello World");
  return 0;
}

При сборке не забыть указать обязательные параметры:

[orca@blizzard my_c]$ g++ `pkg-config opencv --libs` helloworld.cpp -o helloworld

Запустить программу:

[orca@blizzard my_c]$ ./helloworld

Эксперимент №1. Захват видео с веб-камеры

Подключить веб-камеру, убедиться, что она работает. Собрать и запустить тестовую программу (captureCamera.cpp):

#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <stdlib.h>
#include <stdio.h>

CvCapture* capture =0;
IplImage* frame =0;

int main(int argc, char* argv[])
{
  // Получить подключенную камеру
  CvCapture* capture = cvCreateCameraCapture(CV_CAP_ANY);
  assert( capture && "Не удалось найти камеру");

  // Определить ширину и высоту кадра
  double width = cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH);
  double height = cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT);

  //printf("[i] %.0f x %.0f\n", width, height );

  /* Используемые переменные:
   * counter            счётчик для снимков
   * filename           имя файла для снимков
   * currentPosition    текущий номер кадра
   * message            сообщение с указанием номера кадра
   * font                       шрифт, используемый в сообщении
   * pt                 позиция где отображается сообщение
   * size                       размер кадра
   */

  int   counter=0;
  char  filename[512];
  int   currentPosition=0;
  char  message [30];

  CvFont font;
  cvInitFont( &font, CV_FONT_HERSHEY_SIMPLEX, 0.5, 0.5, 0, 1, CV_AA);
  CvPoint pt = cvPoint( 20, 20 );

  cvNamedWindow("capture", CV_WINDOW_AUTOSIZE);
  CvSize size = cvSize(width,height);
  CvVideoWriter *writer = cvCreateVideoWriter("myVideo.avi",CV_FOURCC('D','I','V','X'),15,size);

  printf("[i] press Enter for capture image and Esc for quit!\n\n");

  while(true){
    // Получить кадр
    frame = cvQueryFrame( capture );
    // Показать кадр в окне
    cvShowImage("capture", frame);
    // Напечатать в кадре сообщение с номером этого кадра
    sprintf(message, "position: %d", currentPosition);
    cvPutText(frame, message, pt, &font, CV_RGB(150, 150, 150) );
    // Записать кадр в видеофайл, увеличить счётчик кадров
    cvWriteFrame(writer, frame);
    currentPosition++;

    // Следить за нажатием клавиш клавиатуры
    char c = cvWaitKey(33);
    if (c == 27) {
    // Нажата клавиша ESC -> завершить программу
      break;
    }
    else if(c == 10) {
    // Нажата клавиша Enter -> сохранить кадр как JPG-файл
    // Для ОС MS Windows код клавиши: 13
      sprintf(filename, "Image%d.jpg", counter);
      printf("[i] capture... %s\n", filename);
      cvSaveImage(filename, frame);
      counter++;
    }
  }

  // Освободить ресурсы
  cvReleaseCapture(&capture);
  cvReleaseVideoWriter(&writer);
  cvDestroyWindow("capture");
  return 0;
}

Эксперимент №2. Интеллектуальный захват видео

Изменив предыдущий код, можно написать программу, которая записывает видео только в случае изменения сюжета кадра. Программу можно использовать в системе видеонаблюдения. Камера неподвижна и направлена на наблюдаемый объект. После запуска программы начинается запись видео в файл, но если изображение статично, т.е. не происходит никаких изменений, то кадры пропускаются.

В основе алгоритма лежит сравнение двух соседних кадров (текущего и предыдущего). Если разницы нет, то кадр пропускается. Сравнение кадров осуществляется сличением их гистограмм.

Код программы (intrusionDetect.cpp):

#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctime>

/* Используемые переменные:
 * counter              счётчик для снимков
 * filename             имя файла для снимков
 * currentPosition      текущий номер кадра
 * message              сообщение с указанием номера кадра
 * font                 шрифт, используемый в сообщении
 * pt                   позиция где отображается сообщение
 * size                 размер кадра
 * capture              указатель на структуру чтения видео-потока с камеры
 * writer               указатель на структуру для видеозаписи
 * currentFrame         указатель на текущий кадр
 * previousFrame        указатель на предыдущий или вспомогательный кадр
 * HSV                  каналы одноимённой модели (тон, насыщенность, яркость)
 */

int     counter = 0;
char    filename [512];
int     currentPosition = 0;
char    message [30];
double  histogramComparasion = 0.0;

time_t seconds = time(NULL);
tm* timeinfo = localtime(&seconds);
const char* format = "%A, %B %d, %Y %I:%M:%S";

CvFont font;
CvPoint pt;
CvSize size;

CvCapture* capture = 0;
CvVideoWriter* writer = 0;
IplImage* currentFrame = 0;
IplImage* tempFrame = 0;

CvHistogram* histogram = 0;
CvHistogram* currentHistogram = 0;
CvHistogram* previousHistogram = 0;

CvHistogram* calculateHistogram(IplImage* sourceImage);

IplImage* drawHistogram (CvHistogram* histogram, int HBins, int SBins);

int main(int argc, char* argv[])
{
  Получить подключенную камеру
  capture = cvCreateCameraCapture(CV_CAP_ANY);
  assert( capture && "Не удалось найти камеру");

  // Определить ширину и высоту кадра
  double width = cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH);
  double height = cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT);
  //printf("[i] %.0f x %.0f\n", width, height );
  cvInitFont( &font, CV_FONT_HERSHEY_SIMPLEX, 0.5, 0.5, 0, 1, CV_AA);
  pt = cvPoint( 20, 20 );
  // результат работы в файле myVideo.avi
  cvNamedWindow("capture", CV_WINDOW_AUTOSIZE);
  size = cvSize(width,height);
  writer = cvCreateVideoWriter("myVideo.avi",CV_FOURCC('D','I','V','X'),15,size);
  printf("[i] press Enter for capture image and Esc for quit!\n\n");

  while(true){
    // Получить кадр
    currentFrame = cvQueryFrame( capture );
    // Напечатать в кадре сообщение с номером этого кадра
    //sprintf(message, "position: %d", currentPosition);
    seconds = time(NULL);
    timeinfo = localtime(&seconds);
    strftime(message, 80, format, timeinfo);
    cvPutText(currentFrame, message, pt, &font, CV_RGB(150, 0, 0) );
    // Показать кадр в окне
    cvShowImage("capture", currentFrame);

    // Вычисление гистограмм двух соседних кадров
    if (currentPosition == 0) {
      cvWriteFrame(writer, currentFrame);
      previousHistogram = calculateHistogram(currentFrame);
    }
    currentHistogram = calculateHistogram(currentFrame);

    // Сравнение гистограмм по корреляционному методу
    // 1 – максимальное соответствие, -1 – максимальное несоответствие, 0 – нет никакой корреляции.
    histogramComparasion = cvCompareHist( previousHistogram, currentHistogram, CV_COMP_CORREL );
    // 0.997 - порог срабатывания (подбирается экспериментально)
    if (histogramComparasion < 0.997)
    {
      printf("Method CV_COMP_CORREL. Difference at position %d: %f \n", currentPosition, histogramComparasion);
      cvWriteFrame(writer, currentFrame);
    }

    previousHistogram = currentHistogram;

    // Увеличить счётчик кадров
    currentPosition++;
    // Следить за нажатием клавиш клавиатуры
    char c = cvWaitKey(33);
    if (c == 27) {
    // Нажата клавиша ESC -> завершить программу
      break;
    }
    else if(c == 10) {
    // Нажата клавиша Enter -> сохранить кадр как JPG-файл
    // Для ОС MS Windows код клавиши: 13
      sprintf(filename, "Image%d.jpg", counter);
      printf("[i] capture... %s\n", filename);
      cvSaveImage(filename, currentFrame);
      histogram = calculateHistogram(currentFrame);
      tempFrame = drawHistogram ( histogram, 30, 32);
      sprintf(filename, "Histogram%d.jpg", counter);
      printf("[i] capture... %s\n", filename);
      cvSaveImage(filename, tempFrame);
      counter++;
    }
  }
  // Освободить ресурсы
  cvReleaseCapture(&capture);
  cvReleaseVideoWriter(&writer);
  cvDestroyWindow("capture");
  return 0;
}

// Функция вычисления/построения гистограммы
CvHistogram* calculateHistogram(IplImage* sourceImage)
{
  int HBins = 30;
  int SBins = 32;
  int histogramSize[] = { HBins, SBins };
  float HRanges[] = { 0, 180 };
  float SRanges[] = { 0, 255 };
  float* ranges[] = { HRanges, SRanges };

  IplImage* HSVImage = 0;
  IplImage* channelH = 0;
  IplImage* channelS = 0;
  IplImage* channelV = 0;
  CvHistogram* histogram = 0;

  HSVImage = cvCreateImage( cvGetSize(sourceImage), 8, 3 );
  cvConvertImage( sourceImage, HSVImage, CV_BGR2HSV );

  channelH = cvCreateImage( cvGetSize(sourceImage), 8, 1 );
  channelS = cvCreateImage( cvGetSize(sourceImage), 8, 1 );
  channelV = cvCreateImage( cvGetSize(sourceImage), 8, 1 );

  IplImage* channelsHS[] = { channelH, channelS };
  cvCvtPixToPlane( HSVImage, channelH, channelS, channelV, 0 );

  histogram = cvCreateHist(
    2, histogramSize, CV_HIST_ARRAY, ranges, 1
  );

  cvCalcHist( channelsHS, histogram, 0, 0 );
  cvNormalizeHist( histogram, 1.0 );

  cvReleaseImage( &HSVImage );
  cvReleaseImage( &channelH );
  cvReleaseImage( &channelS );
  cvReleaseImage( &channelV );

  return histogram;
}

IplImage* drawHistogram (CvHistogram* histogram, int HBins, int SBins)
{
  int scale = 10;
  float maxValue = 0;
  IplImage* histogramImage = 0;

  histogramImage = cvCreateImage( cvSize( HBins * scale, SBins * scale ), 8, 3);
  cvZero( histogramImage );

  cvGetMinMaxHistValue( histogram, 0, &maxValue, 0, 0 );

  for( int h = 0; h < HBins; h++ )
  {
    for( int s = 0; s < SBins; s++ )
    {
      float binValue = cvQueryHistValue_2D( histogram, h, s );
      int intensity = cvRound( binValue * 255 / maxValue );
      cvRectangle( histogramImage,
           cvPoint( h*scale, s*scale ),
           cvPoint( (h+1)*scale - 1,
           (s+1)*scale - 1),
           CV_RGB(intensity,intensity,intensity),
           CV_FILLED);
    }
}
  return histogramImage;
}