четверг, 15 ноября 2012 г.

С++ Дата из строки ( c миллисекундами)

Случилась мне необходимость считать из FITS файла время и дату снимка. В этом посту я расскажу, как считать атрибуты с FITS файла и простой способ перевести время и дату из строки в реальные дату и время. Кому интересно, прошу пожаловать под кат...
1 часть - чтения атрибутов FITS файла
Начнём с более простого - вспомним и прошлого моего поста,как мы читали fits файл:
 pFits.reset(new FITS(path,Read,true));
    image = &pFits->pHDU();// get main image from file
    image->readAllKeys();//read Keys
    image->read(contents); // read DATA to valarray
Я ведь не просто так выполнял тогда readAllKeys. Этот метод считывает все дополнительные параметры файла FITS. У PHDU есть ещё один интересный метод - keyWord(). Он возвращает ассоциативный массив(map), ключом которого является строка(название атрибута), а значением - ссылка на специальный тим Keyword (значение атрибута).То есть, этот метод возвращает map<string,*Keyword>. Как работать с map я не буду рассказывать, а из Keyword можно считать в строку с помощью его метода value(). В моем файле дата записана под ключом DATE, а время - TIME-OBS. Вот и считаем сначала в ассоциативный массив, а потом из ассоциативного массива получим дату и время по ключу.
    Keyword* Date = keys.at("DATE");
    Keyword* Time = keys.at("TIME-OBS");

    std::string Dateval;
    std::string Timeval;

    Date->value(Dateval);
    Time->value(Timeval);
Теперь у нас есть 2 строки - дата и время.
2 часть - Перевод из строки в дату
Киким классом даты и времени вам удобно пользоваться? QDateTime? tm? time_t? - Не важно.Любой из ваших любимых типов вы можете создать, если у вас в интовых значениях есть кол-во годов, месяцев, дней, часов, минут, секунд, миллисекунд ... В начале, мне приглянулся тип tm, но в нем нет миллисекунд, поэтому, я создал свой struct для времени. Вот такой:
struct my_tm
{
    int tm_msec;
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;
    int tm_yday;
    int tm_isdst;
};
А читаем мы в него с помошью замечательной функции sscanf вот таким образом:
 my_tm tm1;
 sscanf(Dateval.c_str(),"%4d-%2d-%2d",&tm1.tm_year,&tm1.tm_mon,&tm1.tm_mday);
 sscanf(Timeval.c_str(),"%2d:%2d:%2d.%3d",&tm1.tm_hour,&tm1.tm_min,&tm1.tm_sec,&tm1.tm_msec);
Вот теперь, мы можем создать любой, необходимый нам класс времени и даты:
    QDate qdate(tm1.tm_year,tm1.tm_mon,tm1.tm_mday);
    QTime qtime(tm1.tm_hour,tm1.tm_min,tm1.tm_sec);
    QDateTime qdatetime(qdate,qtime);
А самое вкусное - то, что функция sscanf реализована в самых разных языках кроме Си, а это значит, что данный метод работает очень много где....
Читать дальше......

четверг, 1 ноября 2012 г.

Чтение - запись Fits C++

В прошлый раз я рассказал, как подключить библиотеку для работы с файлами FITS к g++ в линукс. Сегодня мы займёмся непосредственно чтением картинки, небольшим её редактированием и записью в новую картинку. На данном этапе мы не будем учитывать сотни возможностей формата Fits(большое кол-во слоев изображения, дополнительные параметры , многомерные таблицы и т.д. ). В качестве преобразования изображения, мы возьмём инвертирование. Мы просто составим программу вот такого алгоритма


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

понедельник, 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





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

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

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

вторник, 21 августа 2012 г.

Управляемая сцена openGl

В прошлый раз я обещал управляемую сцену и сферу из примитивов -  Принимайте :-)

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


Первое, что мы сделаем - Управляемую сцену. Охота, чтобы по нашему миру можно было путешествовать, как в компьютерных играх: с помощью мышки и клавиатуры WASD. Вспомним наш прошлый класс SceneGl 

class SceneGL : public QGLWidget


Он наследуется от QGLWidget, а QGLWidget наследуется от QWidget, QWidget может принимать и обрабатывать события клавиатуры и мыши. Воспользуемся этим - для этого дополним наш класс SceneGl такими функциями:


    void mousePressEvent(QMouseEvent* pe);
    void mouseMoveEvent(QMouseEvent* pe);
    void mouseReleaseEvent(QMouseEvent* pe);
    void wheelEvent(QWheelEvent* pe);
    void keyPressEvent(QKeyEvent* pe);

На данном этапе нам стоит отдалиться от кода и подумать о концепции необходимого нам функционала. Что мы хотим видеть в итоге?
  • Клавиши W,S - движение вперёд и назад соответственно;
  • Движение мыши, при зажатой левой кнопке - вращение камеры. 
  • Клавиши со стрелками - движение камеры.
  • Пробел - возврат на исходную позицию.
  • Esc - выход из программы.
  • Вращение колёсика мыши- изменение чувствительности скорости вращения к движению мыши.
Для обеспечения данного функционала мне понадобятся такие переменные:

    QPoint ptrMousePosition;


    GLfloat xRot;//поворот камеры относительно X
    GLfloat yRot;//поворот камеры относительно Y
    GLfloat zRot;//поворот камеры относительно Z
    GLfloat zTra;// перемещение камеры по оси Z( происходит перд поворотом)
    GLfloat nSca;// чувствительность мыши



Обрабатывать нажатие кнопки будем в функции keyPressEvent и там, с помощью case определять нажатую клавишу, и изменять параметры. Для удобоваримого чтения кода, и ради хорошего стиля( стиль - не пустой звук, так правда удобней), выделим на каждое изменение параметра по функции:



    void scale_plus();
    void scale_minus();
    void rotate_up();
    void rotate_down();
    void rotate_left();
    void rotate_right();
    void translate_down();
    void translate_up();
    void defaultScene();


Их названия говорят сами за себя. 

Заканчиваем с концепцией, пора перейти к реализации. Дабы не путать функциональный код и тривиальный код управления, я выделил отдельный файл controlescene.cpp.
Вот его содержимое:
#include "scenegl.h"
#include <QtGui>
#include <math.h>

void SceneGL::mousePressEvent(QMouseEvent* pe)
{
   ptrMousePosition = pe->pos();
}

void SceneGL::mouseReleaseEvent(QMouseEvent* pe)
{

}

void SceneGL::mouseMoveEvent(QMouseEvent* pe)
{

   xRot  = 180/nSca*(GLfloat)(pe->y()-ptrMousePosition.y())/height();
   zRot  = 180/nSca*(GLfloat)(pe->x()-ptrMousePosition.x())/width();

   ptrMousePosition = pe->pos();

   updateGL();
}

void SceneGL::wheelEvent(QWheelEvent* pe)
{
   if ((pe->delta())>0) scale_plus(); else if ((pe->delta())<0) scale_minus();

   updateGL();
}

void SceneGL::keyPressEvent(QKeyEvent* pe)
{
   switch (pe->key())
   {
      case Qt::Key_Plus:
         scale_plus();
      break;

      case Qt::Key_Equal:
         scale_plus();
      break;

      case Qt::Key_Minus:
         scale_minus();
      break;

      case Qt::Key_Up:
         rotate_up();
      break;

      case Qt::Key_Down:
         rotate_down();
      break;

      case Qt::Key_Left:
        rotate_left();
      break;

      case Qt::Key_Right:
         rotate_right();
      break;

      case Qt::Key_S:
         translate_down();
      break;

      case Qt::Key_W:
         translate_up();
      break;

      case Qt::Key_Space:
         defaultScene();
      break;

      case Qt::Key_Escape:
         this->close();
      break;
   }

   updateGL();
}

void SceneGL::scale_plus()
{
   nSca = nSca*1.1;
}

void SceneGL::scale_minus()
{
   nSca = nSca/1.1;
}

void SceneGL::rotate_up()
{
   xRot  = 1.0;
}

void SceneGL::rotate_down()
{
   xRot -= 1.0;
}

void SceneGL::rotate_left()
{
   zRot  = 1.0;
}

void SceneGL::rotate_right()
{
   zRot -= 1.0;
}

void SceneGL::translate_down()
{
   zTra -= 0.05;
}

void SceneGL::translate_up()
{
   zTra  = 0.05;
}

void SceneGL::defaultScene()
{
   xRot=0; yRot=0; zRot=0; zTra=0; nSca=1;
}





Перейдём к изменению scenegl.cpp.Добавьте в конструктор класса вызов функции обнуления переменных defaultScene(); и измените paintGL так:

void SceneGL::paintGL(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glTranslatef(0.0f, 0.0, -4.0f);
    glTranslatef(0.0f, 0.0, zTra);
    glRotatef(xRot, 1.0f, 0.0f, 0.0f);
    glRotatef(yRot, 0.0f, 1.0f, 0.0f);
    glRotatef(zRot, 0.0f, 0.0f, 1.0f);и
    ...
Теперь про перемещение. 
Обратите внимание на то, что сначала я перемещаюсь, а потом поворачиваю оси, тем самым я поворачиваю из вокруг уже смещённого положения. В пространстве порядок действий очень важен. На этом рисунке показаны обе последовательности и результаты, к которым они приводят.


Теперь я хочу поговорить про актёров на нашей сцене. Всё, что мы хотим нарисовать должно вызываться в функции repaint. Я хочу дать возможность помещать объекты на сцену динамически. Для этого я придумал такую технологию :
Я создаю абстрактный класс GLobject, в нём создаю виртуальную функцию paint, на сцене создаю вектор объектов типа GLobject и в функции repaint вызываю функцию paint для каждого объекта из моего вектора. Создать объект GLobject мы не можем(класс абстрактный),но мы можем от него наследовать и в классе-наследнике переопределить функцию paint, и в ней рисовать то, что хотим


Ближе к делу. Вот класс GLobject
class ObjectGL : public QObject
{
    Q_OBJECT
public:
    explicit ObjectGL(QObject *parent = 0);
    virtual void paint()=0;
};

Я хочу, чтоб объекты жили своей жизнью во время работы программы. Для этого я делаю дополнительный слой абстракции -  AliveObjectGL. В создадим переменную QTimer, и ещё одну виртуальную функцию-слот live(). Эта функция будет вызываться таймером, в ней потомки будут меня параметры характеризующие их состояние. Вот код классов GLobject и AliveObjectGL :
#ifndef OBJECTGL_H
#define OBJECTGL_H

#include <QGLWidget>
#include <GL/glu.h>
#include <QObject>
#include <QTimer>
class ObjectGL : public QObject
{
    Q_OBJECT
public:
    explicit ObjectGL(QObject *parent = 0);
    virtual void paint()=0;
};



class AliveObjectGL : public ObjectGL
{
    Q_OBJECT
public:
    QTimer *timer;
    explicit AliveObjectGL(QObject *parent = 0,int delay = 10);
    void setdelay(int delay);
private:
        int delay;
public slots:
     virtual  void live()=0;

};


#endif // OBJECTGL_H


И реализация:
#include "objectgl.h"

ObjectGL::ObjectGL(QObject *parent) :
    QObject(parent)
{
}

AliveObjectGL::AliveObjectGL(QObject *parent,int delay) :
    ObjectGL(parent)
{
    this->delay = delay;
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(live()));
    timer->start(delay);
}

void AliveObjectGL::setdelay(int delay)
{
    this->delay = delay;
    timer->stop();
    timer->start(delay);
}


В private SceneGL создаю вектор указателей  QVector<AliveObjectGL*> actors;

После прорисовки осей рисую все объекты.
void SceneGL::paintGL(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glTranslatef(0.0f, 0.0, -4.0f);
    glTranslatef(0.0f, 0.0, zTra);
    glRotatef(xRot, 1.0f, 0.0f, 0.0f);
    glRotatef(yRot, 0.0f, 1.0f, 0.0f);
    glRotatef(zRot, 0.0f, 0.0f, 1.0f);

    drawAxis();
    foreach (AliveObjectGL* actor, actors){
        glPushMatrix();
        actor->paint();
        glPopMatrix();
    }

}






Теперь создадим класс реального объекта. Я обещал сферу? Получайте:
#ifndef SPHEREGL_H
#define SPHEREGL_H
#include "objectgl.h"
#include "math.h"
class SphereGL : public AliveObjectGL
{
    Q_OBJECT
public:
    explicit SphereGL(QObject *parent = 0);


    float radius; /радиус сферы
    GLfloat rotz;   // её вращение вокгруг оси z ( меняется таймерм)
    int init();
    void paint();
    QColor*color;
public slots:
    void live();

};

#endif // SPHEREGL_H



А вот то, как она реализована.
#include "spheregl.h"
#include <QtCore>
SphereGL::SphereGL(QObject *parent) :
    AliveObjectGL(parent)
{
    init();
}

int SphereGL::init()
{
    rotz=0;radius = 1;
    color = new QColor(255,255,255,255);
}

void SphereGL::paint()
{

    glRotatef(20,1,0,0);
    glRotatef(rotz,0,0,1);
    float x,y,z;
    float X=-M_PI,Y=0;
    float Z=0;
    float accuracy = 0.1;
      glColor4f(color->redF(),color->greenF(),color->blueF(),color->alphaF());
    glBegin(GL_TRIANGLE_STRIP);
              while(X<M_PI)
              {
               while(Y<2*M_PI)
               {
               x=radius*cos(X)*cos(Y);
               y=radius*cos(X)*sin(Y);
               z=radius*sin(X);
              // qDebug()<<x<<y<<z;

               glVertex3f(x,y,z);

               x=radius*cos(X)*cos(Y);
               y=radius*cos(X accuracy)*sin(Y);
               z=radius*sin(X);
             //  qDebug()<<x<<y<<z;

               glVertex3f(x,y,z);

               x=radius*cos(X accuracy)*cos(Y);
               y=radius*cos(X)*sin(Y);
               z=radius*sin(X accuracy);
              // qDebug()<<x<<y<<z;

               glVertex3f(x,y,z);
               Y =accuracy;
                 }
                 Y=0;
                 X =accuracy;
               }
              glEnd();

}

void SphereGL::live()
{

    rotz =1.0f;
}


Это простая реализация параметрического уравнения сферы. Перед каждой точкой я добавил цвет по 3 координатам - это делает сферу разноцветной.

Теперь, при инициализации сцены добавим в наш вектор новую сферу. Как видите, она крутится, и мы можем мышкой перемещать её!



Это конечно интересно, но я хочу планету, а у планеты есть спутники, они движутся по орбитам. Давайте посмотрим на небо, как там обстоят дела: есть чёрная дыра, вокруг которой все движется, включая наше солнышко, вокруг солнышка движется планета, вокруг планеты луна и вокруг луны мы можем запустить спутник, и если кто-то выкинет мусор из окошка спутника, у этого спутника то-же появится спутник. Исходя из всего этого я постулирую: у каждого объекта может быть один или несколько спутников, которые движутся по эллиптической орбите вокруг него, и каждый объект может являться спутником для другого объекта, при этом объект не может стать своим собственным спутником. Самостоятельный объект я назову планетой,  а для организации спутников, я создам объект "Орбита", который не имеет права существовать сам по себе, он должен иметь планету, которая по ней движется и вокруг которого она движется. Любую планету можно прилепить к любой орбите, любую орбиту можно прилепить к любой планете, кроме случая, когда планета-центр и планета -спутник совпадают. Орбита должна характеризоваться параметрами эллипса, ещё, кроме вращения планеты по орбите я сделал вращение орбиты относительно планеты, этот случай не имеет места в реальности(на сколько я знаю), но это красиво и интересно.Для всего этого составим диаграмму классов:

Вот такое применение ООП( обожаю эту технологию). Внизу приведён код. Обратите внимание, что из главного окна вызывается прорисовка одной единственной планеты, а она в сою очередь вызывает прорисовки всех орбит которые на ней, а орбиты вызывают прорисовки планет, которые по этим орбитам крутятся. Красота да и только! После реализации всего этого у нас вот такой результат:




Код для запуска и просмотра реализации кольца, и движения по его траектории окружности можете посмотреть тут:
https://dl.dropbox.com/u/95314428/ForBlog/ControledScene.zip
Спасибо за внимание :-) В следующий раз хочу вам рассказать про замечательную функцию- gluLookAt. С её помощью мы сможем перемещаться по миру не смотря всё-время в центр координат, как это происходит в вышеприведённом видео.

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

суббота, 4 августа 2012 г.

Помещение актёров на сцену OpenGl Qt

В прошлом посте я рассказал про создание OpenGl окна. Сегодня я расскажу , как туда поместить объект. Сначала создадим окно , как описано тут http://alexkutsan.blogspot.com/2012/04/opengl-qt.html .

Всё не так сложно.
Изменять я буду  функцию paintGL(). В ней происходит отрисовка.Вызывается она всякий раз, когда окно перерисовывается. Мы нарисуем  8 угольник. Определим его вершины:


Да, мы можем заставить компьютер считать все эти косинусы и синусы, но зачем? Компьютер будет раскладывать в ряд каждое выражение. Лучше попросим любого 7-миклассника посчитать нам эти значения. Он нам с первого взгляда всё выведет (стыдно, да?)


Ближе к коду.


void SceneGL::paintGL(){
  glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  glBegin(GL_POLYGON);
      glVertex3f( 0.5f, 1.0f,0.0f);
      glVertex3f(1.0f,0.5f,  0.0f);
      glVertex3f( 1.0f,-0.5f,0.0f);

      glVertex3f(0.5f,-1.0f,0.0f);
      glVertex3f(-0.5f,-1.0f,0.0f);
      glVertex3f(-1.0f,-0.5f,0.0f);

      glVertex3f(-1.0f,0.5f,0.0f);
      glVertex3f(-0.5f,1.0f,0.0f);
   glEnd();
}



Немного описания.

Мы работаем уже в матричной модели. Изменяем ведь саму модель - glMatrixMode(GL_MODELVIEW);

Мы очищаем буфер цветов -  glClear(GL_COLOR_BUFFER_BIT); , ведь у нас появляются здесь цвета( пока-что только белый)

Загружаем единичную матрицу -  glLoadIdentity(); . Никаких трансформаций над матрицей мы не делаем, поэтому этот шаг не обязателен,но делать его принято. Попробуйте вставить
...
glRotated(30,0,0,1);
glBegin(GL_POLYGON);
...
Вы  увидите, что матрица повернётся относительно единичной. Теперь уберите  glLoadIdentity();. Видите? Матрица поворачивается всякий раз при перерисовке окна относительно прошлого её состояния. Это потому, что функция glRotated(30,0,0,1); не поворачивает объект, это мы с вами думаем, что она поворачивает объект. glRotated умножает текущую матрицу на матрицу поворота, параметры которой мы задаём.

Всё серо и грустно.
Давайте добавим радости - добавим цвета.
Перед каждой точкой будем задавать цвет функцией glColor3f. Её параметры - RGB (0.0f-1.0f);
  glBegin(GL_POLYGON);
       glColor3f(1,0,0);
      glVertex3f( 0.5f, 1.0f,-1.0f);
       glColor3f(0,1,0);
      glVertex3f(1.0f,0.5f,  -1.0f);
       glColor3f(0,0,1);
      glVertex3f( 1.0f,-0.5f,-1.0f);

       glColor3f(1,0,1);
      glVertex3f(0.5f,-1.0f,-1.0f);
       glColor3f(1,1,0);
      glVertex3f(-0.5f,-1.0f,-1.0f);
       glColor3f(1,1,1);
      glVertex3f(-1.0f,-0.5f,-1.0f);

       glColor3f(0.5,0.5,0.5);
      glVertex3f(-1.0f,0.5f,-1.0f);
       glColor3f(0,0.5,1);
      glVertex3f(-0.5f,1.0f,-1.0f);
      glEnd();



 
И результат:



Хочу, чтоб всё вертелось!
Когда мы смотрим на объект, мы смотрим на него сверху.
Для простоты , сейчас мы повертим его относительно оси Z.
Если вы посмотрите код выше, то увидите, что наш 6-угольник находился в плоскости Z на уровне  -  -1. Давайте это изменим, и поставим его на уровень 0. И воспользуемся функцией смещения glTranslated(0,0,-3); Тем самым, мы отведём от глаз шестиугольник на 3 шага.

Для того, чтоб повернуть этот шестиугольник , используется функция   glRotatef. Она принимает 4 параметра:
angel - угол на который нужно повернуть объект.
x - поворачивать относительно Ох?
y - поворачивать относительно Оу?
z - поворачивать относительно Oz?

Давайте создадим private переменную  int rot; И создадим public slot void update();
Внутри update напишем.
void SceneGL::update()
{
    rot++;
    updateGL();
}

Теперь, изменим код initializeGL
void SceneGL::initializeGL(){
    glClearColor(0.0f,0.0f,0.0f,1.0f);
    glEnable(GL_DEPTH_TEST);
    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(10);
    
}

Как видите, мы запускаем таймер, и через каждые 10 миллисекунд будет выполняться update(), тем самым увеличивая переменную rot и перерисовывая сцену. Стоит изменить функцию paintGL
void SceneGL::paintGL(){
    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslated(0,0,-4);
    glRotatef(rot,0,0,1);
    ...
После загрузки единичной матрицы я поворачиваю сцену на rot градусов, а rot постоянно меняется таймером. Запускайте, любуйтесь.

Теперь, давайте повертим наш шести угольник не только относительно Z, а ещё относительно X и Y. Для этого изменим glRotatef(rot,0,0,1); на glRotatef(rot,1,1,1);. Для большей наглядности нарисуем Оси координат. Создайте private функцию

void SceneGL::drawAxis()
{
    glLineWidth(3.0f);
    // до вызова команды ширина равна 9 пикселю по умолчанию
    // устанавливаем цвет последующих примитивов
    glColor4f(1.00f, 0.00f, 0.00f, 1.0f);
    // ось x кvoid Scene3D::drawAxis()
    {
        glLineWidth(3.0f);
        // до вызова команды ширина равна 9 пикселю по умолчанию

        // устанавливаем цвет последующих примитивов
        glColor4f(1.00f, 0.00f, 0.00f, 1.0f);
        // ось x красного цвета
        glBegin(GL_LINES); // построение линии
        glVertex3f( 9.0f,  0.0f,  0.0f); // первая точка
        glVertex3f(-9.0f,  0.0f,  0.0f); // вторая точка
        glEnd();

        QColor halfGreen(0, 128, 0, 255);
        qglColor(halfGreen);
        glBegin(GL_LINES);
        // ось y зеленого цвета
        glVertex3f( 0.0f,  9.0f,  0.0f);
        glVertex3f( 0.0f, -9.0f,  0.0f);

        glColor4f(0.00f, 0.00f, 1.00f, 9.0f);
        // ось z синего цвета
        glVertex3f( 0.0f,  0.0f,  9.0f);
        glVertex3f( 0.0f,  0.0f, -9.0f);
        glEnd();
    }

}

Теперь изменим функцию paintGL вот так:
    ...
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslated(0,0,-4);
    glRotatef(30,1,1,0);
    drawAxis();
    glRotatef(rot,1,1,1);
    ...
и функцию initializeGL
void SceneGL::initializeGL(){
    glClearColor(0.0f,0.0f,0.0f,1.0f);
    glEnable(GL_DEPTH_TEST);
    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    timer->start(10);

}
Обратите внимание на glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); и glEnable(GL_DEPTH_TEST);. Я включил тест глубины, если его не включить, то openGl не будет учитывать дальность объектов. Виден будет объект, которые нарисуется первым. С включённым тестом глубины будет виден тот объект, который ближе к наблюдателю(к нам). Ещё я поместил сюда второй glRotatef, который поворачивает сцену на 30 градусов по X и Y - это даст нам удобный угол просмотра. После этого я рисую оси и поворачиваю сцену на rot градусов по всем осям, и рисую 6-угольник. Благодоря этой последовательности прорисовки, оси у меня остаются всегда в 30 градусах, а объект крутится.
Итог получается вот такой:
 
 В следующий раз я расскажу как сделать управляемую сцену, чтоб мы в ней могли перемещаться динамически. Ещё я хочу рассказать, как с помощью примитивов нарисовать сферу. На сегодня всё.Спасибо всем, кто это прочёл. Исходники можно получить здесь : Arctors.zip
Читать дальше......

среда, 1 августа 2012 г.

Gnome3 Linux Mint

Что-то мне надоели постоянные проблемы с системой. Охота сидеть и писать код. Решил поставить Linux Mint,но меня ждали плохие новости. Как известно, в Mint используется оконное окружение - mate. Мне оно не нравится. Я хочу gnome3. При установке пакетов gdm, gnome и gnome-shell, исталятор валится с указанием на существование файла /usr/share/pixmaps/nobody.png. Его удаление не помогает. Информации об этом в интернете нету.Оказалось, что Mintовский mdm(mint display manager)- это будто-бы gdm, но mdm.Для корректной работы gdm, mdm надо удалить. Удаляете и ставите спокойно gdm и gnome. Лично мне - нравится mdm. Я удалил mdm, поставил gdm,gnome,gnome-shell, удалил gdm, поставил mdm. Всё... Работает как надо :-)
Читать дальше......

пятница, 1 июня 2012 г.

Больше гибкости!!! И прыгающие шарики.

Прошла пара дней с тех пор,как я выпустил свою первую библиотеку. И вот, я выпускаю первое к ней обновление, и програмку “Скачущие шарики”, иллюстрирующую её применение . Если интересно, прошу под кат.
Если вы посмотрите в исходный код, то теперь там найдёте новый класс ExeptionMiscarriage.
public class ExeptionMiscarriage extends Throwable{
    private String info ="Object didn't ready";
    public ExeptionMiscarriage(String info){
        this.info =info;
    }

    public ExeptionMiscarriage() {
       
    }
    /**
     * @return the info
     */
    public String getInfo() {
        return info;
    }

    /**
     * @param info the info to set
     */
    public void setInfo(String info) {
        this.info = info;
    }
    public void addInfo(String info) {
        this.info =  this.info   info;
    }   
}
Не сложно догадаться, что это Исключение “выкидыша”. Что это значит? Теперь, если пользователь не заполнил все необходимые поля в панели создания объекта и нажал на кнопочку “ОК”, вы можете сформировать это исключение, и наполнить его информаицией. И пользователю будет выдано модальное окошко ошибки, в котором будет ваша информация.
Диалог не прекратится, а продолжит действовать, пока пользователь не введёт всю необходимую информацию для формирования вашего объекта, или же не нажмёт “Отмена”. Второе улучшение библиотеки касается расширяемости.  По ходу написания программы “Скачущие шарики”, мне понадобилось использовать мою библиотеку не совсем в стандартной ситуации. Я хотел сделать диалог выбора цвета. Панель выбора цвета существует - JColorChooser. И вот ведь незадача: я не могу сообщить её в конструктор своего диалога, так как она не является наследником FactoryPanel. Вот он, камень преткновения и проектировочный тупик. Наследовать от 2-х классов в Java нельзя. Даже, если я наследую от JColorChooser и реализую интерфейс ObjectFactory. То это не сможет явно привести к FactoryPanel во -первых :потому, что так делать нельзя. Если у нас есть:
 
class A{}
interface I{}
class B extends A,implements I{}
class С extends A,implements I{}
Мы не можем Объект класса C явно привести к B. Но, даже, если бы это было возможно-в конкретной ситуации этого сделать было бы нельзя, так как у JColorChooser нет в потомках JPanel. Но зато, у него есть JComponent. JComponent является наследником всех Swing UI элементов. Поэтому, я пересмотрел свою библиотеку и в свой диалог я буду принимать JComponent. Конечно, в целях обратной совместимости, я оставил FactoryPanel, но переименовал её в FactoryComponent. Это не вызовет проблем при разработке панели в визуальном редакторе. Вы по-прежнему, после разработки переопределяете наследника с JPanel на FactoryComponent. Но из за мухи в супе, описанной выше, такой фокус не пройдёт с JColorChooser. Поэтому, я решил, сделать ещё один конструктор в ComplexDialog. ComplexDialog(JFrame parent, Object mainpanel, String title, boolean modal); Как видите - Тип главной панели-настройщика – Object. То есть , я могу сообщить туда всё, что душе угодно. Но в самом конструкторе я проверяю:
 
        settingscomponent = (JComponent)mainpanel;
        objectFactory = (ObjectFactory)mainpanel;
Если вы скормили моему диалогу какое-то непотребство - вывалится исключение о несоответствии типов.  Вернёмся к задаче: Нам надо скормить JColorChooser в ComplexDialog. Если скормим без приправы, ComplexDialog- выплюнет исключение, как я и описывал выше. Поэтому, мы наследуем от JColorChooser и при этом реализуем интерфейс ObjectFactory.
 
    public class MyColorChoser extends JColorChooser implements complexdialog.ObjectFactory{

        @Override
        public Object getResult() throws ExeptionMiscarriage {
           return getColor();
   }
Видите, как классно! Теперь мы можем создать объект типа MyСolorСhoser и успешно скормить его моему диалогу в качестве Object. Так как JColorChooser потомок JComponent, а MyСolorСhoser ещё и реализовывает ObjectFactory- значит диалог скушает этот объект с большим удовольствием. Здесь, в папке examples/example2 ,лежит программа “Скачущие мячики” ,в которой реализованы 2 диалоговых окна моей библиотеки. Первое окно (добавления мячика) показывает, как работают исключения-выкидыши. Второе- показывает, как скормить чужие компоненты моей библиотеке. Дабы вам не было сильно скучно - в самой програмке скачут шарики, как в реальной жизни: с ускорением, вязкостью воздуха, коэффициентом упругости и прочими вкусностями. Для добавления шарика-нажмите кнопочку “Add”,она вызовет мой диалог, и вы не сможете нажать OK, пока не заполните все поля и не выберете цвет. Если вы читаете эту статью, наверно вы - программист. А ,значит , вас не испугает перспектива залезть в код и посмотреть, как добавляются шарики. Я специально, сразу кинул в цикле 20 шариков с разной начальной скоростью и прочим. Поэкспериментируйте с разным количеством шариков, динамической начальной скоростью(очень интересны получаются функциональные зависимости), меняющимися цветами, динамическим коэффициентом упругости.Попробуйте изменить вязкость среды, время обновления рассчётов, временной коэффициент- я специально создал его, чтобы при очень больших масштабах( я сбрасывал мячик с 10 километров на начальной скорости 100 метров в секунду) всё происходило немного быстрей, чем в реальной жизни. Ибо скучно. Только осторожно! Меня эта штука затянула на целый вечер, поэтому, я и пишу статью сегодня, а не вчера :-) Всем спасибо. Постараюсь радовать вас новыми интересными идеями, библиотеками, програмками и прочим. И ,конечно, до бесконечности совершенствовать библиотеку ComplexDialog.


За редакцию хочется выразить благодарность,Алисе Брыль. 
Читать дальше......

понедельник, 28 мая 2012 г.

Моя первая библиотека, или Сложные Диалоги в Java (SWING)

Добрый вечер. Сегодня я хочу предоставить на ваш суд свою первую библиотеку. Прошу не судить строго, но буду рад конструктивной критике и предоложениям по развитию. Если же кто-то захочет помочь в модификации, буду только счастлив. Теперь про библиотеку. Она позволяет вам создавать сложные диалоговые окна. Что это? Зачем? Стандартная библиотека позволяет создавать простые диалоговые окна(выбор Да\Нет, ввод строки, выдор варианта из списка). А что, если вам требуется диалоговое окно, результатом котого будет целый объект. Например, Диалоговое окно выбора места на карте, а результатом будет объект адреса, на котоый кликнул пользователь. Вот, с помошью моей библиотеки, вы сможете это сделать быстро и просто.
Скачать сие творение можно от сюда ComplexDialog . Там же можно присоедениться к разработке.

Теперь про то, как ей пользоваться. Подключив библиотеку(Есть много способов это сделать. Зависит от среды разработки и ваших вкусовых предпочтений) ,вы получаете 2 класса и 1 интерфейс.

Интрфейс ObjectFactory имеет один единственный метод, который возврашает значение указаного вами типа.

Первый класс - ComplexDialog. Это, непосредственно ,класс диалога. Он насделуется от JDialog .Для работы с ним, вы должны указать тип который диалог обязан вернуть. После вызова его конструктора и вызова метода show\setVisible(true) ,можно смело вызывать метод getReturnedStatus(), который вернёт сформированный пользователем объект. Программа приостановится, пока пользователь не нажмёт на OK или Canel. Если Пользователь жмёт Canel, getReturnedStatus возвращает нулевой указатель.

Для того, чтоб детальнее понять ComplexDialog, стоит изучить FactoryPanel. Это абстрактный класс. Он абсолютно пустой. Наследуется от JPanel и реализует интерфейс ObjectFactory. От него вам стоит наслелдовать панель, в которой будет вестись настройка вашего объекта. Обратите внимание, класс нетипизирован и дать ему тип вы должны такой же, как и в ComplexDialog. Потому что ComplexDialog использует метод getResult() для возвращения значения. На самом деле, вы можете создать панельку и в визуальном редакторе, но потом подредактировать код, изменив extends высшей панельки на FactoryPanel, и реализовать метод getResult(),как того требует интерфейс ObjectFactory.

Для создания ComplexDialog нужно:
JFrame parent - окно -родитель(осталось от JDialog);
FactoryPanel mainpanel - панелька, в которой происходит настройка необходимого вам типа; 
String title - надпись на окошке (осталось от JDialog) ; 
boolean modal - модальность окна (осталось от JDialog); 


Для более наглядного объяснения, хочу привести пример практического использования этой библиотеки. Одним из самых простых и достаточно часто необхидимых примеров ,наверное, будет DatePicker, то есть Диалог, в котором пользователь выберет какую- либо дату. 


Наш "ХелоВорлд" будет просить пользователя ввести дату его рождения и подсчитает количество дней прожитых пользователем. Для начала, Создадим формочку, которая наследуется от FactoryPanel. Для подтверждения своих слов, я создал её в визуальном редакторе, позже реализовал внутри необходимый функционал и реазиловал метод getResult() ,который возвращает объект типа java.util.Date, сформированный пользователем. FactoryPanel:
package alexkutsan_blogspot;

import complexdialog.FactoryPanel;
import java.util.Calendar;
import java.util.Date;
import javax.swing.DefaultComboBoxModel;
/**
 *
 * @author alex
 */

public class DatePickerPanel extends FactoryPanel<Date>{

    /**
     * Creates new form DatePickerPanel
     */
    public DatePickerPanel() {
        super();
        initComponents();
        myinitUI();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void initComponents() {

        YearBox = new javax.swing.JComboBox();
        MonthBox = new javax.swing.JComboBox();
        DayBox = new javax.swing.JComboBox();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();

        YearBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
        YearBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                YearBoxActionPerformed(evt);
            }
        });

        MonthBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
        MonthBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                MonthBoxActionPerformed(evt);
            }
        });

        DayBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));

        jLabel1.setText("%u0413%u043E%u0434");

        jLabel2.setText("%u041C%u0435%u0441%u044F%u0446");

        jLabel3.setText("%u0414%u0435%u043D%u044C");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(YearBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(6, 6, 6)
                .addComponent(jLabel2)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(MonthBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel3)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(DayBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(27, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(25, 25, 25)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(YearBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(MonthBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(DayBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel1)
                    .addComponent(jLabel2)
                    .addComponent(jLabel3))
                .addContainerGap(35, Short.MAX_VALUE))
        );
    }// </editor-fold>

    private void YearBoxActionPerformed(java.awt.event.ActionEvent evt) {                                        
       int m = MonthBox.getSelectedIndex()   1;
                    int y = YearBox.getSelectedIndex()   (Calendar.getInstance().get(Calendar.YEAR) - CountYears);
                    int n = m != 2 ? m > 7 ? 30   m % 2 == 1 ? 0 : 1 : 30   m % 2 : y % 4 == 0 && y % 100 != 0 || y % 400 == 0 ? 29 : 28;
                    Integer[] Days = new Integer[n];
                    for (int i = 1; i < n   1; i  ) {
                        Days[i - 1] = i;
                    }
                    DayBox.setModel(new DefaultComboBoxModel(Days));
    }                                       

    private void MonthBoxActionPerformed(java.awt.event.ActionEvent evt) {                                         
                    int m = MonthBox.getSelectedIndex()   1;
                    int y = YearBox.getSelectedIndex()   (Calendar.getInstance().get(Calendar.YEAR) - CountYears);
                    int n = m != 2 ? m > 7 ? 30   m % 2 == 1 ? 0 : 1 : 30   m % 2 : y % 4 == 0 && y % 100 != 0 || y % 400 == 0 ? 29 : 28;
                    Integer[] Days = new Integer[n];
                    for (int i = 1; i < n   1; i  ) {
                        Days[i - 1] = i;
                    }
                    DayBox.setModel(new DefaultComboBoxModel(Days));
    }                                        

    // Variables declaration - do not modify
    private javax.swing.JComboBox DayBox;
    private javax.swing.JComboBox MonthBox;
    private javax.swing.JComboBox YearBox;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    // End of variables declaration

    private void myinitUI() {
     Date cur = new Date(System.currentTimeMillis());
            int curentYear = Calendar.getInstance().get(Calendar.YEAR);
            Integer[] Years = new Integer[CountYears];
            for (int i = curentYear - CountYears; i < curentYear; i  ) {
                Years[i - (curentYear - CountYears)] = i;
            }
            Integer[] Month = new Integer[12];
            for (int i = 1; i <= 12; i  ) {
                Month[i - 1] = i;
            }
            Integer[] Data31 = new Integer[31];
            Integer[] Data30 = new Integer[30];
            Integer[] Data29 = new Integer[29];
            Integer[] Data28 = new Integer[28];
            for (int i = 1; i < 32; i  ) {
                if (i < 31) {
                    Data30[i - 1] = i;
                }
                if (i < 30) {
                    Data29[i - 1] = i;
                }
                if (i < 29) {
                    Data28[i - 1] = i;
                }
                Data31[i - 1] = i;
            }
            int m = Month[0];
            int y =Years[0];
            int n = m != 2 ? m > 7 ? 30   m % 2 == 1 ? 0 : 1 : 30   m % 2 : y % 4 == 0 && y % 100 != 0 || y % 400 == 0 ? 29 : 28;
            Integer[] Days = new Integer[n];
            for (int i = 1; i < n   1; i  ) {
                Days[i - 1] = i;
            }
            YearBox.setModel(new DefaultComboBoxModel(Years));
            MonthBox.setModel(new DefaultComboBoxModel(Month));
            DayBox.setModel(new DefaultComboBoxModel(Days));
    }
    int CountYears = 100;

    @Override
    public Date getResult() {
            Integer y =(Integer) YearBox.getSelectedItem();
            Integer m =(Integer) MonthBox.getSelectedItem();
            Integer d =(Integer) DayBox.getSelectedItem();
            Date date = new Date(y-1900, m-1, d);
            return date;
    }
}
Вот такая панелька
У неё незамысловатый функционал .Внимания заслуживает лишь моя функция рассчёта количества дней в месяце.
 int n = m != 2 ? m > 7 ? 30 m % 2 == 1 ? 0 : 1 : 30 m % 2 : y % 4 == 0 && y % 100 != 0 || y % 400 == 0 ? 29 : 28; где 
m - месяц(1..12)
y - год.

 Как видите, в функции getResult() формируется и возвращается Дата, введённая пользователем. Теперь главный класс:
 


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package alexkutsan_blogspot;
import complexdialog.ComplexDialog;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.swing.JOptionPane;
/**
 *
 * @author alex
 */
public class Alexkutsan_blogspot {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws ParseException, IOException, URISyntaxException {


        // Date today = new Date(System.currentTimeMillis());
        Date today = new Date(System.currentTimeMillis());
        DatePickerPanel p = new DatePickerPanel();
        ComplexDialog<Date> dp = new ComplexDialog<Date>(null,p,"%u0412%u0432%u0435%u0434%u0438%u0442%u0435 %u0434%u0430%u0442%u0443 %u0432%u0430%u0448%u0435%u0433%u043E %u0440%u043E%u0436%u0434%u0435%u043D%u0438%u044F", true);
        dp.setVisible(true);
        Date born = dp.getReturnedStatus();
        if (born != null) {
            long difference = today.getTime() - born.getTime();
            long daycount = TimeUnit.MILLISECONDS.toDays(difference);
            JOptionPane.showMessageDialog(dp, String.format("%u0417%u0435%u043C%u043B%u044F %u0432%u0430%u0441 %u043D%u043E%u0441%u0438%u0442 %u0432%u0441%u0435%u0433%u043E-%u0442%u043E %s %u0434%u043D%u0435%u0439 ", daycount), "%u0412%u044B %u0442%u0430%u043A %u043C%u043E%u043B%u043E%u0434%u044B", JOptionPane.INFORMATION_MESSAGE);
        }
        //URLLabel myEgo = new URLLabel(new URI("http://alexkutsan.blogspot.com/"),"%u041F%u0440%u043E%u0433%u0440%u0430%u043C%u043C%u0443 %u0440%u0430%u0437%u0440%u0430%u0431%u043E%u0442%u0430%u043B %u0410%u043B%u0435%u043A%u0441%u0430%u043D%u0434%u0440 %u041A%u0443%u0446%u0430%u043D");
        //JLabel mersi = new JLabel("%u0411%u043B%u0430%u0433%u043E%u0434%u0430%u0440%u043D%u043E%u0441%u0442%u0438:\n \t %u0410%u043B%u0435%u043A%u0441%u0430%u043D%u0434%u0440 %u041A%u0438%u043B%u0438%u043C%u043D%u0438%u043A \t \n %u0410%u043B%u0438%u0441%u0430 %u0411%u0440%u044B%u043B%u044C");
        JOptionPane.showMessageDialog(dp,new About(), "%u0410%u0432%u0442%u043E%u0440: ", JOptionPane.INFORMATION_MESSAGE);
        System.exit(0);
    }
}

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

        ComplexDialog<Date> dp = new ComplexDialog<Date>(null,p,"%u0412%u0432%u0435%u0434%u0438%u0442%u0435 %u0434%u0430%u0442%u0443 %u0432%u0430%u0448%u0435%u0433%u043E %u0440%u043E%u0436%u0434%u0435%u043D%u0438%u044F", true);
        dp.setVisible(true);
        Date born = dp.getReturnedStatus();
Выглядит диалог Так:
По нажатию, у меня в born лежит либо дата, которую выбрал пользователь, либо null. Если Дата != null, то выскочит сообщение c подсчитаными днями. Работать это будет только ,если вы ещё создадите эти классы. Они не нуждаются в дополнительном описании, разве что URLLable. Но о ней я расскажу позже. Скачать пример можно по этой ссылке ComplexDialog , в каталоге Examples.
 

//The contents of this file are subject to the Mozilla Public License Version 1.1
//(the "License"); you may not use this file except in compliance with the 
//License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
//
//Software distributed under the License is distributed on an "AS IS" basis,
//WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
//for the specific language governing rights and
//limitations under the License.
//
//The Original Code is "The Columba Project"
//
//The Initial Developers of the Original Code are Frederik Dietz and Timo Stich.
//Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003. 
//
//All Rights Reserved.
package alexkutsan_blogspot;


import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import javax.swing.JLabel;

public class URLLabel extends JLabel {

  boolean entered = false;

  boolean mousehover;
  private  URI site ;
  public URLLabel(URI uri) {
    this(uri, uri.toString());
  }

  public URLLabel(URI uri, String str) {
    super(str);
    this.site = uri;
    setForeground(Color.blue);
    mousehover = false;
    setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
    this.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() > 0) {
                    if (Desktop.isDesktopSupported()) {
                        Desktop desktop = Desktop.getDesktop();
                        try {
                          
                            desktop.browse(site);
                            System.exit(0);
                        } catch (IOException ex) {
                            
                        }
                    } 

                }
            }
        });
  }

  public void mouseEntered(MouseEvent e) {
    entered = true;
    if (mousehover) {
      repaint();
    }
  }

  public void mouseExited(MouseEvent e) {
    setCursor(Cursor.getDefaultCursor());
    entered = false;

    if (mousehover) {
      repaint();
    }
  }

  public void mousePressed(MouseEvent e) {
  }

  public void mouseReleased(MouseEvent e) {
  }

    @Override
  public void paint(Graphics g) {
    super.paint(g);

    if (entered || !mousehover) {
      Rectangle r = g.getClipBounds();

      g.drawLine(0, r.height - this.getFontMetrics(this.getFont()).getDescent(), this
          .getFontMetrics(this.getFont()).stringWidth(this.getText()), r.height
          - this.getFontMetrics(this.getFont()).getDescent());
    }
  }
}


}


 

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package alexkutsan_blogspot;

import java.net.URI;
import java.net.URISyntaxException;

/**
 *
 * @author alex
 */
public class About extends javax.swing.JPanel {

    /**
     * Creates new form About
     */
    public About() {
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        try{
            jLabel2 = new URLLabel(new URI("http://alexkutsan.blogspot.com/"), "%u0410%u043B%u0435%u043A%u0441%u0430%u043D%u0434%u0440 %u041A%u0443%u0446%u0430%u043D");
            jLabel3 = new javax.swing.JLabel();
            jLabel4 = new javax.swing.JLabel();
            jLabel5 = new javax.swing.JLabel();

            jLabel1.setText("%u041F%u0440%u043E%u0433%u0440%u0430%u043C%u043C%u0443 %u0440%u0430%u0437%u0440%u0430%u0431%u043E%u0442%u0430%u043B:");

        }
        catch(URISyntaxException e){

        }
        jLabel2.setText("%u0410%u043B%u0435%u043A%u0441%u0430%u043D%u0434%u0440 %u041A%u0443%u0446%u0430%u043D");

        jLabel3.setText("%u0411%u043B%u0430%u0433%u043E%u0434%u0430%u0440%u043D%u043E%u0441%u0442%u0438:");

        jLabel4.setText("%u0410%u043B%u0435%u043A%u0441%u0430%u043D%u0434%u0440 %u041A%u0438%u043B%u0438%u043C%u043D%u0438%u043A");

        jLabel5.setText("%u0410%u043B%u0438%u0441%u0430 %u0411%u0440%u044B%u043B%u044C");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(jLabel1)
                            .addComponent(jLabel3))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jLabel2))
                    .addGroup(layout.createSequentialGroup()
                        .addGap(41, 41, 41)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(jLabel5)
                            .addComponent(jLabel4))))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel1)
                    .addComponent(jLabel2))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel3)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel4)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel5))
        );
    }// </editor-fold>                        
    // Variables declaration - do not modify                     
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    // End of variables declaration                   
}


В написании статьи хочу выразить благодарность этим людям: 
    Алиса Брыль; 
    Александр Килимник;
Читать дальше......