понедельник, 29 октября 2012 г.

std::valarray и std::slice в С++

Разбирал стандартные примеры из CCfits. Эти ребята очень активно(не всегда оправдано) используют стандартную библиотеку C++. Как оказалось, я в ней плаваю. Сегодня я узнал о замечательном шаблоне массива С++ std - valarray и классом - slice,который позволяет очень удобно стучатся одновременно к многим элементам массива. Я провёл некоторую сравнительную характеристику и построил графики быстродействия. Хочу поскорее рассказать вам, что к чему. valarray - одна из реализаций массива C++.Она действительно удобная. Написана она для выполнения векторных операций. Посмотрите только, какие вкусности нам предлагают. Одна из этих вкусностей - класс slice, который работает как указатель сразу на большое кол-во элементов valarray. К примеру, у нас есть RGB картинка, записанная в одномерный массив. std::valarray row(n); и мы хотим дать картинке определённый цвет( каждый 3*i пиксель заменить на значение R, 3*i+1 на значение G, 3*i+2 на значение B). Как мы делали это раньше?
unsigned char red = 255;
 unsigned char green = 128;
 unsigned char blue = 0;
 for(int i =0;i<n; i = i 3)
  row[i]=red;
 for(int i =1;i<n;i =3)
  row[i]=green;
 for(int i =2;i<n;i =3)
  row[i]=blue;
А с помощью групповых указателей мы можем поступить так:
    slice red(1,n/3,3);
 slice green(2,n/3,3);
 slice blue(3,n/3,3);
 
 row[red]=255;
 row[green]=128;
 row[blue]=0;
Удобно не правда ли?
Slice принимает 3 параметра:
  1.  slice::start  -  первый элемент в выборке 
  2.  slice::size -  количество элементов в выборке
  3.  slice::stride - шаг или расстояние между элементами, которые выбраны
 Это правда удобно и я захотел проверить быстродействие этого метода, в сравнении со старым-дедовским. Буду проверять 3 метода:


   1-ый : Simple - старый добрый массив указателей в связке с циклом for
   2-й :  Valarray - массив valarray в связке с циклом for
   3-й :  Slice - массив valarray в связке с объектом slice

Суть эксперимента:  есть массив из n элементов. В этом массиве нужно заменить каждый 3 элемент массива на какое-либо значение. Замена производится 3 вышеперечисленными способами с замером быстродействия каждого. Кол-во элементов массива варьируется от          1 000 000 до 100 000 000 c с шагом в 1 000 000. Для каждого метода и кол-ва элементов массива проверка осуществляется 50 раз с нахождением среднего времени. По окончании замера производительности, построить графики зависимости времени исполнения замены от  кол-ва элементов массива по 3 методам. Проанализировать динамику каждого из методов.
Цель эксперимента:
  Сравнить быстродействие 3 методов доступа к элементам массива на запись. 

Для построения графиков я использовал библиотеку QT. Рисовал просто на QLable, её и вывел на экран.

Ниже представлена программа реализующая вышеуказанную задачу:
#include <QApplication>
#include "QPainter"
#include <QLabel>
#include <QRect>
#include <QPixmap>
#include <QDebug>

#include <iostream>
#include <valarray>
#include <unistd.h>
#include <time.h>
#define ZAMENA 1

using namespace std;

long Simple(long count){
 int * na = new int[count];
 long t1 = clock();
 for(int i = 1;i<count;i =3)
  na[i]=ZAMENA;
 long t2 = clock();
 delete[] na;
 return t2-t1;

}

long Valarray(long count){
 std::valarray<int> a(count);
 long t1 = clock();
 for(int i = 1;i<count;i =3)
  a[i]=ZAMENA;
 long t2 = clock();
 return t2-t1;
}



long Slice(long count){
 std::valarray<int> a (count);
 long t1 = clock();
 slice s (1,count/3,3);
 a[s]=ZAMENA;
 long t2 = clock();
 return t2-t1;
}


int main(int argc, char *argv[]) {
 int w =500;
 int h = 500;
 QColor SimplePen(255,0,0);
 QColor SlicePen(0,255,0);
 QColor ValarrayPen(0,0,255);
 
 
 QApplication app(argc, argv);
 QLabel *MainLable = new QLabel("Graphs");
 MainLable->setBaseSize(w,h); 
 QPixmap MainPixMap(w,h);
 MainPixMap.fill(MainLable,0,0);
 QPainter MainPainter(&MainPixMap);
 MainPainter.fillRect(MainPainter.viewport(),QColor(255,255,255));
    MainPainter.setPen(SimplePen);
    MainPainter.drawLine(10,10,100,10);
    MainPainter.drawText(110,10,"Simple");
    MainPainter.setPen(SlicePen);
    MainPainter.drawLine(10,25,100,25);
    MainPainter.drawText(110,25,"Slice");
    MainPainter.setPen(ValarrayPen);
    MainPainter.drawLine(10,40,100,40);
    MainPainter.drawText(110,40,"Valarray");


    /*
     * Example of drawing y = f%u043E%u043E(x)
    int count = 1000000;
    int dx = 1000;
    double maxy= foo(count);
    for(double x = dx;x<count;x =dx){
  double y = foo(x);
  double y2 = foo(x dx);
  double x2 = x dx; 
  MainPainter.drawLine((x/count)*w,h-((y/maxy)*h),(x2/count)*w,h-((y2/maxy)*h));
 }
 */
 
  int count_tests =50;
  long n = 100000000;
  long dx  = 1000000;
  long *args = new long[n/dx];
  long *SimpleTimes = new long[n/dx];
  long *SliceTimes = new long[n/dx];
  long *ValarrayTimes = new long[n/dx];
  
  for (int i = 0 ;i<n/dx;i  ){
   args[i]=dx*i;
  }
  
  long max =0;
  
  qDebug()<<"Simple begins";
  
  for (int j=0 ;j<n/dx-1;j  ){
   long S=0;
   for (int i = 0;i<count_tests;i  )
    S =Simple(args[j]);
   S = S/count_tests;
   SimpleTimes[j]=S;
   max = max<S?S:max;
   if (j%10==0)
    qDebug()<<j;
  }
  qDebug()<<"Simple ends";
  qDebug()<<"Valarray begins";
  for (int j=0 ;j<n/dx-1;j  ){
   long S=0;
   for (int i = 0;i<count_tests;i  )
    S =Valarray(args[j]);
   S = S/count_tests;
   ValarrayTimes[j]=S;
   max = max<S?S:max;
   if (j%10==0)
    qDebug()<<j;
  }
  qDebug()<<"ValarrayTimes ends";
  
  qDebug()<<"Slice begins";
  for (int j=0 ;j<n/dx-1;j  ){
   long S=0;
   for (int i = 0;i<count_tests;i  )
    S =Slice(args[j]);
   S = S/count_tests;
   SliceTimes[j]=S;
   max = max<S?S:max;
   if (j%10==0)
    qDebug()<<j;
  }
  qDebug()<<"Slice ends";
  qDebug() <<"max"<< max;
  
  for (int j=0 ;j<n/dx-2;j  ){
   MainPainter.setPen(SimplePen);
   MainPainter.drawLine(w*args[j]/n,h-(h*SimpleTimes[j]/max),w*args[j 1]/n,h-(h*SimpleTimes[j 1]/max));
   MainPainter.setPen(SlicePen);
   MainPainter.drawLine(w*args[j]/n,h-(h*SliceTimes[j]/max),w*args[j 1]/n,h-(h*SliceTimes[j 1]/max));
   MainPainter.setPen(ValarrayPen);
   MainPainter.drawLine(w*args[j]/n,h-(h*ValarrayTimes[j]/max),w*args[j 1]/n,h-(h*ValarrayTimes[j 1]/max));
  }
 MainLable->setPixmap(MainPixMap);
 MainLable->show();
 return app.exec();

}
Выполнялась она долго. Если есть желание - компильте на здоровье: текстовку выше выкиньте в файл main.cpp, создайте файл tryValarray.pro следующего содержания:
TARGET = app
SOURCES +=   main.cpp \

Выполнить
qmake
make
./app 

На выходе получите изображение с графиками. Справа - результат  на моей машине.
Сделаем выводы:
Зависимость прямо пропорциональная у всех методов. Но у старого дедовского намного -больше коэффициент возрастания.

2 и 3 методы практически идентичны по времени исполнения и активно выигрывают у старого дедовского. Однако, третьим методом пользоваться удобней и быстрей с точки зрения скорости написания кода. Очень советую valarray и slice при обработке массивов больших размерностей в олимпиадах - важность критерия быстродействия программы и скорости написания кода там очевидна.


Читать дальше......

пятница, 19 октября 2012 г.

Маленькие Заметки о проблемах и их решениях


Здесь я буду выкладывать небольшие проблемы, с которыми я сталкиваюсь в совместной жизни с компьютером.

Читать дальше......

четверг, 18 октября 2012 г.

Подключение библиотеки CCFits

Судя по выдаче google, мало кого из русскоязычных любителей волнует астрономический формат данных fits. Для его чтения написано множество библиотек для различных языков, но нет примеров простейшего HelloWorld. Сегодня я не буду писать программу, а просто расскажу ,как подключить библиотеки для работы с fits файлами к g++. В С++ для чтения fits используется библиотека CCfits . Она тащит за собой библиотеку cfitsio, которая используется для чтения fits формата в Си. По сути, ССfits -это - C++ обёртка для cfitsio. Вот её мы и установим.

Шаг первый - установка cfitsio:
 Для начала - скачаем и установим cfitsio. Для этого скачайте архив  (или последнюю версию с вышеуказанной ссылки), распакуйте его и войдите в папку cfitsio. В ней выполните

 ./configure --prefix=/usr/local/lib/cfitsio/


(предварительно создав папку /usr/local/lib/cfitsio/) 

Дальше по обыкновению :
make
sudo make install

И проверка 
make testprog
Если всё прошло без ошибок - переходим к шагу 2.
Шаг второй установка ccfits:
Скачиваем и распаковываем архив , входим в папку CCfits. 
выполняем:

./configure --with-cfitsio=/usr/local/lib/cfitsio/
make
sudo make install

Обратите внимание на папку /usr/local/lib/cfitsio/  - необходимо указать путь вашей установки cfitsio!

Шаг последний - проверка 

Создайте где-то файл first.cpp такого содержания:
#include <CCfits>

int main (){
 return 0;
}



для компиляции используйте такие флаги к g++:
g++ -I/usr/local/include/CCfits -I/usr/local/lib/cfitsio/include/  -lCCfits first.cpp

Всё, библиотека инклудится, значит можно спокойно идти спать. Если вы голодны на информацию -  в качестве продолжения очень советую этот проект ( это с++ с fits и qt).

Читать дальше......

вторник, 16 октября 2012 г.

Структура данных - последовательность CvSeq (контуры в OpenCv)

— Ты функциональщик! - прокричал Сергей на весь оупен-спейс-рум номер 14.
Комната притихла в ожидании развязки.
— Я видел, как ты вчера вечером каррировал и декаррировал прямо за рабочим компьютером!
Неодобрительный ропот и возгласы удивления прокатились по комнате. Кто-то громким шепотом сказал “какой ужас, а я с ним за руку здоровался”.
— Знаешь что, Сергей, — сказал Денис, вставая из-за рабочего стола, — любой нормальный мужчина, если у него всё в порядке, может позволить себе позаниматься функциональным программированием. Это естественно. Каждый хотя бы раз, да пробовал. Зачем только об этом кричать на всю комнату? Я же не кричу, что ты объектно-ориентированный!
Девушки захихикали, кто-то снова громко пробормотал “ну надо же, а по нему и не скажешь”.
Присутствовавший при этом Игорь Матвеевич сильнее вжался в кресло. Только бы никто не узнал про его процедурные наклонности!


Программирование вырабатывает своеобразный способ мышления - мы начинаем смотреть на вещи "из нутри". На рассвете, наблюдая за солнцем, мы тут-же начинаем строить модель солнечной системы, мысленно создавать абстрактный класс-небесное тело, у него список параметров и методов, которые оно должно выполнять, политику доступа и прочее. Анализируя своё время, мы строим циклы, условия происходящего вокруг - думаем, как бы мы это реализовали, при необходимости. Быть может, так во всех творческих профессиях. Художник, наверно, тоже видит,  как смешать краски для портрета, просто разговаривая с человеком. Музыкант мысленно подбирает ноты и тональность для интонаций голоса своей возлюбленной. На и у нас - у программистов, есть разница в мышлении - типы данных. Любой программист баз данных, уже подсознательно, представляет всё вокруг в таблицах и связях между ними, программист на функциональных языках видит структуры со списком аргументов,и функций в которые эти структуры будут попадать, Java\C++  - программист будет видеть всё в списке классов, с наследованием,и порой будет стараться применить любой из паттернов проектирования.


 Мы привыкли к стуктурам типа : массив, переменная, указатель. Сегодня я хочу рассказать об ещё одной удивительной структуре данных - Связный список. На первых курсах университета все учили это, но мало кто применял его на практике. Он кажется очень неудобным на первый взгляд, но это не так. Уже прошло 2-3 года как я занимаюсь программированием, а встретил связный список я только сейчас.  Структура данных называется CvSeq  - это последовательность, но не просто последовательность, это многоуровневая последовательность! Вот её официальное описание

#define CV_SEQUENCE\_FIELDS() \
    int flags; /* micsellaneous flags */ \
    int header_size; /* size of sequence header */ \
    struct CvSeq* h_prev; /* previous sequence */ \
    struct CvSeq* h_next; /* next sequence */ \
    struct CvSeq* v_prev; /* 2nd previous sequence */ \
    struct CvSeq* v_next; /* 2nd next sequence */ \
    int total; /* total number of elements */ \
    int elem_size;/* size of sequence element in bytes */ \
    char* block_max;/* maximal bound of the last block */ \
    char* ptr; /* current write pointer */ \
    int delta_elems; /* how many elements allocated when the sequence grows
                  (sequence granularity) */ \
    CvMemStorage* storage; /* where the seq is stored */ \
    CvSeqBlock* free_blocks; /* free blocks list */ \
    CvSeqBlock* first; /* pointer to the first sequence block */

typedef struct CvSeq
{
    CV_SEQUENCE_FIELDS()
} CvSeq;

На данном этапе нас волнуют:
h_next, и  h_prev - это ссылки на последующий и предыдущий элементы последовательности.
Если бы мы ограничились только ими, то последовательность была бы у нас одноуровневая. Но есть ещё элементы : v_next, v_prev -  это следующий и предыдущий элемент по вертикали.
В результате у нас получается что-то похожее на дерево. Наглядно можете увидеть это в вашем дереве каталогов. Как будто, у каждой папки есть ссылка на соседнюю и предыдущую папку,  на папку в которой она лежит и на первую папку, которая лежит в ней.
Вот диаграмма связей небольшого такого дерева:



Как видно из рисунка, у элемента есть ссылка только на 1 дочерний элемент(кроме элементов у которых нет дочерних), но зато у каждого дочернего есть ссылка на своего родителя (кроме корневого элемента).Ещё, у каждого элемента есть ссылка на левый и правый элементы. Важно помнить, что элементы одного уровня по разным веткам не пересекаются.

Стоит обратить внимание, что  список этот не закольцован -  у третьего элемента третего уровня по ветке 2 нет ссылки на первый элемент третьего уровня по ветке 2. Это значит, что его переменая h_next указывает на  NULL. А переменная h_prev  у первого элемента 3 уровня по ветке 2 указывает ,так-же,  в NULL. Но переменные существуют, а значит мы можем их определить сами.

Теперь, когда мы разобрались, что такое последовательность, давайте наглядно её применим. OpenCV - замечательная библиотека( я уже говорил это? ). Она нам поможет извлечь контуры объектов из изображения как раз в такую иерархическую последовательность. Я нарисовал рекурсивное изображение:
>

Там много контуров и в каждом круге есть свои дочерние контуры. Для извлечения контуров в OpenCV используется функция
cvFindContours
int cvFindContours(
   CvArr* image,
   CvMemStorage* storage,
   CvSeq** first_contour,
   int header_size=sizeof(CvContour),
   int mode=CV_RETR_LIST,
   int method=CV_CHAIN_APPROX_SIMPLE,
   CvPoint offset=cvPoint(0,0)
);

Параметры:

image - Исходное 8-битное изображение. Отличные от нуля пикселы обрабатываются как 1, нулевые пикселы остаются 0 – т.е. изображение является монохромным. Чтобы получить такое изображение, можно использовать cvThreshold, cvAdaptiveThreshold или cvCanny. Функция изменяет исходное содержание изображения.
storage - Контейнер найденных контуров.
first_contour - Указатель на первый найденный контур.
header_size - Размер заголовка последовательности, > = sizeof (CvChain) если method=CV_CHAIN_CODE,  и >=sizeof (CvContour) в противном случае.
mode -
   CV_RETR_EXTERNAL – находятся только критические внешние контуры;
   CV_RETR_LIST – находятся все контуры, и помещает их в список
   CV_RETR_CCOMP – находятся все контуры, и записывают их в иерархию с двумя уровнями:верхний уровень – внешние границы компонентов, второй уровень – границы отверстий
   CV_RETR_TREE – находятся все контуры, и записывается полная иерархия вложенных
контуров.
method - Метод аппроксимации (для всех режимов, кроме CV_RETR_RUNS, который использует встроенную аппроксимацию).
  CV_CHAIN_CODE – на выходе очерчивает контур в цепном коде Фримена [1]. Все другие
методы выводят многоугольники;
  CV_CHAIN_APPROX_NONE – переводит все точки с цепного кода в точки;
  CV_CHAIN_APPROX_SIMPLE – сжимает горизонтальные, вертикальные, и диагональные доли; 
  СV_CHAIN_APPROX_TC89_L1,  CV_CHAIN_APPROX_TC89_KCOS  – применяет одну
из разновидностей алгоритма аппроксимации цепочки Teh-Chin.
  CV_LINK_RUNS - использует полностью различный алгоритм поиска контура через соединение горизонтальных долей. Только CV_RETR_LIST режим поиска может использоваться с этим методом.
offset - Смещение, с которым каждая точка контура сдвинута. Это полезно, если контуры извлечены из изображения ROI, и затем они должны быть проанализированы в целом контексте изображения.

В любом ЧБ изображении( а сообщать этой функции можно только ЧБ), перед использованием его в cvFindContours  стоит применить бинаризацию(cvThreshold, cvCanny ...) иначе, все не белые пикселы будут интерпретироваться как чёрные. В данном случае это не обязательно - изображение уже бинаризировано. Результат этой функции будет лежать в переменной first_contour и это как-раз CvSeq. first_contour - указатель на первый контур, и у него есть указатели на правый контур, и на контур внутри него(если такой существует). По сути, мы получаем ссылку на корневой элемент структуры (см. рисунок выше) и от него можем плясать дальше. Уровни не закольцованы - это неудобно, поэтому, давайте закольцуем! Я изобрел такой рекурсивный велосипед для данной задачи:
void loop_levels(CvSeq* level){
// замыкание всех уровней последовательности
 CvSeq* first =  level;
 CvSeq* last =  level;
 while (level){
  if(level->v_next)
   loop_levels(level->v_next);
  if(!level->h_next)last = level;
  level=level->h_next;
 }
 first->h_prev = last;
 last->h_next = first; 
}

После пропуска нашей последовательности через эту процедуру, уровни будут закольцованы и использовать последовательность станет ещё приятней.

Теперь рассмотрим функцию рисования последовательностей на изображение:
void cvDrawContours(
   CvArr *img,
   CvSeq* contour,
   CvScalar external_color,
   CvScalar hole_color,
   int max_level,
   int thickness=1,
   int line_type=8,
   CvPoint offset=cvPoint(0,0)
);


Параметры:
img - Изображение, в которое будут выводиться контуры.
contour - Указатель на первый контур.
external_color - Цвет внешних контуров.
hole_color - Цвет внутренних контуров.
max_level - Максимальный уровень для отображаемых контуров. Если 0, только один контур. Если 1, этот контур и все контуры на том же самом уровне. Если 2, все контуры одинакового уровня и все контуры на один уровень ниже контуров отображаются, и т.д. Если значение отрицательно, функция рисует только дочерние контуры контура до abs(max_level)-1 уровень.
thickness - Толщина контура.

Обратим особое внимание на параметр max_level, в него по сути мы сообщаем максимальную глубину вложенности отображаемых контуров. Для начала, нарисуем все контуры изображения функцией

 cvDrawContours( cnt_img, contours, CV_RGB(255,0,0), CV_RGB(0,255,0),100,1, CV_AA, cvPoint(0,0) );

Как видите, я рисую контуры вплоть до сотого вложенного: внешние контуры - красные, внутренние - зелёные.

Вот так это выглядит:

Если вы внимательно посмотрите на функцию cvDrawContours - увидите, что в неё сообщается параметр max_level . Я смухлевал, сказав, что отображаю все контуры изображения. На самом деле,  я отобразил все контуры вплоть до сотого уровня. Другое дело, что в этом изображении у меня 8 уровней, всего-то.  Если мы в качестве этого параметра укажем 0, то отобразится только текущий контур. Напишем же программу, которая будет перемещаться по контурам в этом изображении. По стрелкам влево и вправо - в пределах 1 уровня, по стрелкам вверх вниз - вертикально меж уровней. Вот её код:
#include "cv.h"
#include "highgui.h"
#include "iostream"
using namespace cv;
using namespace std;


#define WIN_NAME "camera"
#define WIN2_NAME "countours"

int threashold = 100;

void CHThreashocd(int pos){
  threashold = pos;
}
int main(int argc, char* argv[])
{
        cvNamedWindow(WIN_NAME);
        // получаем любую подключённую камеру
        CvCapture *capture = cvCreateCameraCapture(CV_CAP_ANY);
        assert(capture!=0);
        IplImage *frame=0;
        IplImage * im_rgb  = 0;
  IplImage *  im_clean = 0;
  IplImage *im_gray = 0;
  CvSeq* contours = 0;
  CvMemStorage* storage = cvCreateMemStorage(0);
  cvNamedWindow(WIN_NAME,1);
  cvNamedWindow(WIN2_NAME,1);
  cvCreateTrackbar("threashold",WIN2_NAME,&threashold,255,CHThreashocd);
  
        while(true){
                im_rgb = cvQueryFrame( capture );
    im_gray  = cvCreateImage(cvGetSize(im_rgb),IPL_DEPTH_8U,1);
    cvCvtColor(im_rgb,im_gray,CV_RGB2GRAY);
    cvThreshold( im_gray, im_gray, threashold, 255, CV_THRESH_BINARY );
    //cvAdaptiveThreshold(im_gray, im_gray, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C ,CV_THRESH_BINARY, 21, 7);
    cvFindContours( im_gray, storage, &contours, sizeof(CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));
    contours = cvApproxPoly( contours, sizeof(CvContour), storage,CV_POLY_APPROX_DP, 3, 1 );//Апроксимация контуров
    CvSeq* h_next=0;
    for( CvSeq* c=contours; c!=NULL; c=c->h_next )
    {
     if (c!=contours)
     {
      if (c->total<=100) //размер удаляемых контуров
      {
       h_next->h_next=h_next->h_next->h_next;
       continue;
      }
     }
     h_next=c;
    }
    if (contours->total<=100) contours=contours->h_next;
    cvDrawContours( im_rgb, contours, CV_RGB(255,0,0), CV_RGB(0,255,0),2, 1, CV_AA, cvPoint(0,0) );
    cvShowImage(WIN2_NAME, im_rgb );//Вывод изображения в окно
    cvShowImage(WIN_NAME, im_gray );//Вывод изображения в окно
                char c = cvWaitKey(10);
                if (c == 27)  // если нажата ESC - выходим
                        break;
                
        }
        cvReleaseCapture( &capture );
        cvDestroyWindow(WIN_NAME);
        return 0;
}


Bash-скрипт для компиляции этого всего:

CC=g++
CFLAGS=' -I/usr/local/include/opencv -L/usr/local/lib'
LIBRARIES=' -lopencv_core -lopencv_imgproc -lopencv_highgui'

target=idea2
input=idea2.cpp

rm -rf $target

c_string=$CC$CFLAGS' -o '$target' '$input$LIBRARIES
`$c_string`
echo $c_string


Внимательный читатель должен заметить интересную функцию, про которую я не рассказал - cvApproxPoly. Каждый контур, это многоугольник с уймой вершин. Многие из этих вершин на не нужны, но память они занимают и каждая вершина требует обработки, что занимает ещё и ресурсы процессора. На этой задаче это не сильно ощутимо, но при больших объёмах данных, контур лучше упростить( уменьшить кол-во вершин полигона). Этим и занимается cvApproxPoly. Вот описание её параметров:


CvSeq* cvApproxPoly( const void* src_seq, int header_size, CvMemStorage* storage, int method, double parameter, int parameter2=0 );

src_seq -  Последовательность контуров.
header_size- Размер заголовка аппроксимирующей кривой
storage - Хранилище аппроксимированных контуров.
method - Метод аппроксимации; только CV_POLY_APPROX_DP поддержан, который соответствует  алгоритму Дугласа - Пейкера.
parameter - Определенный методом параметр; в случае CV_POLY_APPROX_DP это - желательная точность приближения.
parameter2 -  Если src_seq - последовательность, это означает, должна ли одиночная последовательность быть аппроксимирована или все последовательности на том же самом уровне или ниже src_seq (см.cvFindContours для описания иерархических структур контура).


Вот и всё, если вы внимательно читали статью и код, то алгоритм не нуждается в описании.
А вот видео результата работы:

Сегодня мы освоили такую интересную штукенцию как связный список, а так-же получили некий опыт работы с контурами в OpenCv. Контуры в основном могут использоваться  для распознавания объектов на однородном фоне, или группы объектов на однородном фоне. Они идеально подходят для распознавания текста, и прочего. Вскоре я напишу про сравнение 2-х контуров с помощью их моментов, а на сегодня на этом всё.
Читать дальше......

среда, 3 октября 2012 г.

Python+OpenCv распознавание лиц и определение расстояния до них (версия простая )

Все знают такую функцию в фото-аппаратах, как распознавание лиц? Никогда не задумывались, как она работает? Никогда не хотелось сделать что-то подобное? Например, чтобы ваш компьютер выходил из спящего режима, как только кто-то к нему сел? Применений можно найти море, а тут я расскажу как это сделать.


Мы живём в счастливую для лентяев эпоху - вам уже не нужно купаться в сложной математической теории. Всё сделано за вас, по крайней мере на таком элементарном уровне. Распознавание лиц происходит по признакам Хаара. "Эталон" лица уже давно создан и хорошо обучен, находится он в файле haarcascade_frontalface_alt.xml . Там находится, так называемая, модель лица, а именно "Идеальное" лицо в виде примитивов Хаара :

Это бинаризированая диаграмма яркостей. У любого лица посередине светло(нос), а по краям уходит в тёмное, а потом снова на светлое( щеки). Таких признаков у лица много, и они перечислены в haarcascade_frontalface_alt.xml. А наша программка будет их искать.

 Первый бой (вывод видео на экран) :

  Для начала давайте на экран выведем видео поток. С помощью OpenCV это делается элементарно.




import cv2

cam = cv2.VideoCapture(0)
while 1:
     _,frame =cam.read()
     cv2.imshow("features", frame)
     if cv2.waitKey(10) == 0x1b: # ESC
         print 'ESC pressed. Exiting ...'
         break

Работает? Классно!
Код прост:
Я создаю объект камеры, которая подхватит первую попавшуюся(ваша web-ка)
И всё последующее время я буду читать 1 кадр из камеры и выводить его на экран, каждые десять миллисекунд опрашивать клавиатуру, и, если нажата клавиша с кодом 27(Esc) ,то выйду из бесконечного цикла прямо в ОС.



Битва вторая( использование микроскопа по назначению):

Использовать OpenCV для вывода видео на экран - забивание гвоздей микроскопом. Не будем столь брутальны - начнём  распознавание лиц:



import sys
import cv,cv2
import numpy
cascade = cv.Load('haarcascade_frontalface_alt.xml')

def detect(image):
 bitmap = cv.fromarray(image)
 faces = cv.HaarDetectObjects(bitmap, cascade, cv.CreateMemStorage(0))
 if faces:
  for (x,y,w,h),n in faces:
   pass
   cv2.rectangle(image,(x,y),(x+w,y+h),(255,255,255),2)
 return image

if __name__ == "__main__":
    cam = cv2.VideoCapture(0)
    while 1:
        _,frame =cam.read()
        frame = numpy.asarray(detect(frame))
        cv2.imshow("features", frame)
        if cv2.waitKey(10) == 0x1b: # ESC
            print 'ESC pressed. Exiting ...'
            break

HaarDetectObjects принимает 3 параметра ( где_искать,что искать, где_хранить_промежуточные_данные)
где_хранить_промежуточные_данные - область памяти, которую можно выделить функцией CreateMemStorage.

Уже что-то.

Последний бой ( проявите каплю своего творчества):

Если, проделав это всё, вы гордо скажите, что вы программист, я плюну вам в лицо. Что вы сделали? Ну хоть малость своих мыслей тут? Мы использовали стандартные функции для совершения достаточно тривиальных действий - ни капли математического аппарата. Давайте-же прибавим! А что тут можно сделать? Определим расстояние до лица!
Попытаемся понять, как работает наша камера:



Обратите внимание, что треугольники ACB и DCE подобны. Что это значит?
Уберём из рисунка лишнее и добавим необходимое:

А значит это то , что отношение DE/CH равно отношению AB/FC. Поднеся линейку в 10 сантиметров к веб-камере так, чтоб она поместилась полностью, и измерив расстояние до веб-камеры от линейки, мы можем узнать отношение высоты матрицы нашей камеры к её фокусному расстоянию.( у меня это 1.6) .
Средняя высота лица человека - 15 сантиметров.


Получив из OpenCV размер квадрата лица, просто вычислить, сколько процентов всей высоты кадра оно занимает. Несложно додуматься, что Если лицо влезает в камеру всеми своими 15 сантиметрами , то до веб-камеры - (AB/FC)*12. Если лицо занимает меньше 100% пространства, то оно более удалено. Давайте определим , насколько...





Из рисунка видно, что переместив объект из точек CD в точки EF, его отображение(GH) будет занимать не 100% , а N%(где N<=100). Из свойства подобия треугольников видно, что отношение GH/AB = KL/CD. А ,значит, для наглядности ,я могу упростить модель до треугольника и трапеции:
Что нам отсюда известно?
Мы знаем, отношение AB/FE - это есть наше фокальное расстояние, обозначим его как focal.
реальные размеры AB.

Нам надо узнать расстояние FG.
Треугольники AFB и DFC подобны, значит FE/FG=AB/DC. Из этого равенства видно, что если FG увеличивается в M раз, то либо DC увеличивается в M раз, либо AB уменьшается в M раз. AB уменьшится не может - это реальный размер лица, он всегда 15 сантиметров, А вот DC - может, это размер лица на фотографии, чем лицо дальше, тем размер его меньше. Исходя из этого, можно сделать вывод, что если DC уменьшилось в M раз( или на N%) ,то и FG уменьшилось так же в M раз( или на N%). Если DC у нас равно AB, то GF = EF, если DC меньше AB в M раз (илиN%),то и FG


Теперь ближе к коду:

import sys
import cv,cv2
import numpy
cascade = cv.Load('haarcascade_frontalface_alt.xml')
c=1.6
Sr=15

def detect(image):
 bitmap = cv.fromarray(image)
 faces = cv.HaarDetectObjects(bitmap, cascade, cv.CreateMemStorage(0))
 if faces:
  for (x,y,w,h),n in faces:  
   k=float(w)/bitmap.cols
   S = Sr*c/k
   cv2.rectangle(image,(x,y),(x+w,y+h),(255,255,255),3)
   cv2.putText(image,'S=%s'%(S),(x,y-10), cv2.FONT_HERSHEY_PLAIN, 1.0,(255,255,255))
 return image

if __name__ == "__main__":
    cam = cv2.VideoCapture(0)
    while 1:
        _,frame =cam.read()
        frame = numpy.asarray(detect(frame))
        cv2.imshow("features", frame)
        if cv2.waitKey(1) == 0x1b: # ESC
            print 'ESC pressed. Exiting ...'
            break





Как видите, я задаю отношение размера матрицы к фокальному расстоянию и размер лица человека, и, исходя из этого, считаю его удалённость, в зависимости от процентов занимаемого им места на экране.

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

Читать дальше......