Pokazywanie postów oznaczonych etykietą cimg. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą cimg. Pokaż wszystkie posty

czwartek, 16 lipca 2009

Składowanie obrazów w bazie danych

0 komentarze
Istnieje wiele sytuacji kiedy zapis obrazka w bazie danych może okazać się najlepszym rozwiązaniem i może znacznie uprościć logikę aplikacji. Warto z tego podejścia korzystać kiedy algorytmy przetwarzania obrazu trwają dosyć długo i nie byłoby wskazane ich powtarzanie za każdym razem przy starcie programu. Innym przykładem może być potrzeba zapisania korekt lub obrysów naniesionych przez użytkownika na obrazie. Jest to proces czasochłonny i nikt nie chce robić tego dwa razy.

Oczywiście obrazek zawsze można umieścić w systemie plików, a w samej bazie zapisać ścieżkę dostępu. Jednak w ten sposób musimy dbać o dwie bazy: relacyjną i plikową. Może to powodować niepotrzebne komplikacje i z punktu widzenia szybkości działania programu jest rozwiązaniem wolniejszym.

W tym wpisie zostanie pokazane jak przygotować małą, graficzną bazę danych w oparciu o CImg i Sqlite.

Biblioteka Sqlite świetnie nadaje się do takich zastosowań, ponieważ działa w obrębie tworzonego programu - baza danych jest wbudowywana w aplikację. Jest to zupełnie inne podejście, aniżeli to spotykane w dużych, komercyjnych bazach danych działających w trybie klient-serwer. W tym wypadku mamy bazę plikową, która jest zalecana do pracy z jednym klientem.

Aby rozpocząć pracę z Sqlite należy zainstalować następujące pakiety (przykład dla Ubuntu):

sudo apt-get install sqlite3 libsqlite3-0 libsqlite3-dev

Następnie utworzymy testową bazę danych, a w niej jedną tabelę do gromadzenia obrazów. Będą one zapisywane w polu photo typu blob.

sqlite3 image_db.sqlite3
create table photos(id integer primary key autoincrement, photo blob, filename char, dimx integer, dimy integer, dimz integer, dimv integer);
.q

Poniżej został zaprezentowany przykładowy program pozwalający na dodawanie obrazów do bazy danych oraz ich odczyt. Analiza kodu oraz komentarzy w nim zawartych powinna umożliwić szybkie zrozumienie jego logiki działania.

#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>

#include "CImg.h"

using namespace std;
using namespace cimg_library;

int main(int argc, char **argv){
sqlite3 *db;
char *zErrMsg = 0;
int rc;
int dx,dy,dz,dv;
int rownum=0, num_bytes;
const unsigned char *filename;
const unsigned char *data_buffer;

//Parametry wejściowe
const char* file_i  = cimg_option("-i", (char*)0, "Input image");
const char* db_name = cimg_option("-d", (char*)0, "Database file");
const int option    = cimg_option("-o", 1,"Option (1-show, 2-add)");

//Sprawdzamy poprawność parametrów wejściowych
if (option != 1 && option != 2)
  throw CImgException("Please specify correct option (1-show, 2-add)");

if (db_name==(char*)0)
  throw CImgException("Please specify database file (option -d)");

if (option == 2 && file_i==(char*)0) {
  throw CImgException("Please specify image file name (option -i)");
  exit(1);
}

//Otwarcie bazy danych
rc = sqlite3_open(db_name, &db);
if( rc ){
  fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
  sqlite3_close(db);
  exit(1);
}

//Definicja zapytań
sqlite3_stmt *insert_stmt, *select_stmt;
char insert_str[] = "insert into photos(photo,filename,dimx,dimy,dimz,dimv) values (?,?,?,?,?,?)";
char select_str[] = "select id, photo,filename,dimx,dimy,dimz,dimv from photos";
char begin_str[]  = "begin transaction";
char commit_str[] = "commit transaction";

//Zapis do bazy
if (option == 2) {
  //Odczyt obrazka z pliku
  CImg<unsigned char> image(file_i);

  //Poczatek tranzakcji
  sqlite3_exec   (db, begin_str, NULL, NULL, NULL);
  sqlite3_prepare(db, insert_str, -1, &insert_stmt, NULL);

  //Wstawienie danych do tabeli
  sqlite3_bind_blob(insert_stmt, 1, image.ptr(), image.size()  * sizeof(unsigned char), SQLITE_STATIC);
  sqlite3_bind_text(insert_stmt, 2, file_i,      strlen(file_i)* sizeof(unsigned char), SQLITE_STATIC);
  sqlite3_bind_int (insert_stmt, 3, image.dimx());
  sqlite3_bind_int (insert_stmt, 4, image.dimy());
  sqlite3_bind_int (insert_stmt, 5, image.dimz());
  sqlite3_bind_int (insert_stmt, 6, image.dimv());
  sqlite3_step (insert_stmt);
  sqlite3_reset(insert_stmt);

  //Zakończenie tranzakcji
  sqlite3_exec(db, commit_str, NULL, NULL, NULL);
}
else {  //option==1
  //Odczyt danych z tabeli
  sqlite3_prepare(db, select_str,-1, &select_stmt, NULL);

  while (sqlite3_step(select_stmt) == SQLITE_ROW) {
    //odczytujemy nazwe pliku
    filename = sqlite3_column_text(select_stmt, 2);
    //odczytujemy rozmiary obrazu
    dx = sqlite3_column_int(select_stmt, 3);
    dy = sqlite3_column_int(select_stmt, 4);
    dz = sqlite3_column_int(select_stmt, 5);
    dv = sqlite3_column_int(select_stmt, 6);
    //pobieramy obraz
    int num_bytes = sqlite3_column_bytes(select_stmt,1);
    num_bytes = num_bytes / sizeof(unsigned char);
    data_buffer = new unsigned char[num_bytes];
    data_buffer = (const unsigned char*)sqlite3_column_blob(select_stmt, 1);

    printf("%s (%d,%d,%d,%d)\n",filename,dx,dy,dz,dv);

    //tworzymy i wyświetlamy obraz
    CImg<unsigned char> tmp(data_buffer,dx,dy,dz,dv);
    CImgDisplay main_img(tmp, "Obraz wejsciowy");
    while (!main_img.is_closed);
  }
}

sqlite3_close(db);
return 0;
}

Podczas kompilacji poza standardowymi bibliotekami wymaganymi przez CImg dołączamy również bibliotekę sqlite.

g++ imagedb1.cpp -o imagedb1 -lX11 -lpthread -lsqlite3

Przykładowe wywołanie programu dodające 2 obrazki do bazy:

./imagedb1 -d image_db.sqlite3 -o 2 -i input1.bmp
./imagedb1 -d image_db.sqlite3 -o 2 -i input2.bmp

Wyświetlenie obrazów z bazy:

./imagedb1 -d image_db.sqlite3

Analizując wyżej przedstawiony przykład łatwo można dostrzec zalety korzystania z baz danych w aplikacjach służących do przetwarzania obrazu. Najważniejszą z nich jest możliwość zapisywania stanu aplikacji i kontynuowania pracy po jego kolejnym uruchomieniu.

środa, 3 czerwca 2009

Edycja video w CImg i ffmpeg

0 komentarze
Próba edycji pliku wideo z poziomu C++ może okazać się kłopotliwa jeśli nie korzystamy z dodatkowych bibliotek. Jak wiadomo standardów zapisu / kompresji multimediów jest naprawdę sporo i jeśli poradzilibyśmy sobie z jednym formatem to taką samą pracę należało by wykonać dla pozostałych. W Linuksie może nas w tym wyręczyć narzędzie ffmpeg i zbiór bibliotek, które są w nim zawarte (więcej można przeczytać na Wikipedii).

Aby napisać prosty przykład korzystający z ffmpeg trzeba poznać podstawy API bibliotek libavcodec oraz libavformat. Dla początkującego użytkownika może okazać się problematyczna analiza techniczna dokumentacji i niuansów związanych z kodekami video. Dlatego skorzystamy z innej biblioteki, która zawiera wsparcie dla ffmpeg, a mianowicie CImg (ta mała biblioteka nie przestaje mnie zaskakiwać swoimi możliwościami).

Na początek musimy się upewnić, że mamy zainstalowany pakiet ffmpeg. Możemy wydać polecenie instalacji i jeśli go nie będzie zostanie pobrany i zainstalowany:
sudo apt-get install ffmpeg

Oczywiście będzie również potrzebny testowy film, który będziemy modyfikować. Ja korzystałem z Pixar "Yeti Dance" (Pixar-Yeti_Dance.mpeg).

Następnie przygotowujemy kod programu:
#include <iostream>
#include <ostream>
#include "../CImg.h"

using namespace std;
using namespace cimg_library;

void process_frame(CImg<unsigned char> &frame)
{
  //negatyw obrazu
  cimg_forXYV(frame,x,y,v) { frame(x,y,v) = 255 - frame(x,y,v); }
}

int main()
{
  CImgList<unsigned char> film;
  film.load_ffmpeg("Pixar-Yeti_Dance.mpeg"); //odczyt z pliku

  CImgDisplay disp_film1, disp_film2;

  cimglist_for(film,index) {
    film[index].display(disp_film1);  //wyświetl oryginalną klatkę
    process_frame( film[index] );     //nakładanie filtru
    film[index].display(disp_film2);  //wyświetl zmodyfikowaną klatkę
  }
  while (!disp_film1.is_closed) disp_film1.wait();

  film.save_ffmpeg("Pixar-Yeti_Dance-invert.mpeg"); //zapis
  return 0;
}

Kompilacja za pomocą pomocą polecenia:
g++ -o ffmpeg_test ffmpeg_test.cpp -lX11 -lpthread

Po uruchomieniu programu zobaczymy na ekranie zarówno obraz oryginalny, jak i zmodyfikowany:

Omówiony przykład jest bardzo prosty i nie pokazuje pełnych możliwości ffmpeg. Jednak na etapie projektowania algorytmu wykorzystanie biblioteki CImg może znacznie ułatwić przygotowanie działającego prototypu.

czwartek, 22 stycznia 2009

Nanoszenie wyników detekcji na obraz źródłowy w CImg

0 komentarze
W wyniku segmentacji obrazu otrzymujemy obiekt lub grupę obiektów, które spełniają zadane przez nas kryteria. Dla przykładu, poniżej możemy zobaczyć przekrój mózgu oraz wynik detekcji substancji białej z wykorzystaniem algorytmu Connected Threshold z biblioteki Insight Toolkit. Wydaje się, że całkiem nieźle poszło. Jednak patrzac na te dwa obrazy osobno trudno wizualinie ocenić czy czegoś nie zgubliliśmy lub czy gdzieś nie jest za dużo. Na pewno łatwiej by było po naniesieniu wyniku na obraz źródłowy. O tym właśnie będzie ten wpis.


Korzystając z biblioteki CImg pokażę jak napisać własną funkcję oraz program przydatny w takich sytuacjach.

Podejście pierwsze
Najłatwiej można to oczywiście zrobić iterując po obu obrazach i jeśli w danym punkcie obraz wynikowy ma wartość 255 (biały kolor) to na obrazie źródłowym w tym samym miejscu zmieniamy kolor na inny, wcześniej ustalony. Poniższa funkcja działa właśnie w taki sposób.

template<typename T>
void cimg_nanies_wyniki(CImg<T> &image, CImg<T> &result, unsigned char* color)
{
cimg_forXY(image,x,y) {
if (result(x,y) == 255) {
image(x,y,0) = color[0];
image(x,y,1) = color[1];
image(x,y,2) = color[2];
}
}
}


Patrząc na obraz wynikowy, który powstanie w wyniku wykonania tej funkcji, wydaje się, że mamy to czego potrzebowaliśmy. Jest jednak pewien problem. Pokolorowany obszar przysłania to co jest pod nim i nie widzimy, czy gdzieś nie znajduje się obiekt, który nie należy do poszukiwanego obszaru. Najlepiej w takim wypadku zastosować przeźroczystość podczas kolorowania.




Podejście drugie
Zastosowanie przeźroczystości w CImg wymaga kilku dodatkowych zabiegów aniżeli miało to miejsce w poprzednim przykładzie. Najpierw przygotujemy dodatkowy obraz typy float, który wypełnimy wartością 1.0. Następnie zmienimy wszystkie punkty zgodnie z maską, którą tutaj jest obraz z detekcją. Później ten obraz jest nanoszony na obraz początkowy z użyciem funkcji draw_image, która pozwala na ustalenie przeźroczystości.

template<typename T>
void cimg_nanies_wyniki2(CImg<T> &image, CImg<T> &mask, unsigned char* color, float transp)
{
CImg<T> layer_c = mask;

layer_c.draw_rectangle(0, 0, layer_c.dimx(), layer_c.dimy(), color, 1.0);

CImg<T> layer_f1 = mask;
cimg_forXY(layer_f1,x,y)
layer_f1(x,y) = (layer_f1(x,y,0)==255 && layer_f1(x,y,1)==255 && layer_f1(x,y,2)==255)?0.0f:1.0f;
CImg<T> layer_f2=layer_f1.get_shared_channel(0);

image.draw_image(layer_c,layer_f2,0,0,0,0,1.0f,transp);
}
Po wywołaniu tej funkcji otrzymujemy obraz wejściowy z naniesionym wynikiem detekcji. Teraz już znacznie łatwiej ocenić, czy zastosowany przez nas algorytm segmentacji zadziałał poprawnie.



Przykładowa funkcja main
Dodatkowo została zaprezentowana funkcja main() odbierająca parametry z wiersza poleceń i wywołująca funkcję cimg_nanies_wyniki2(). Po jej skompilowaniu otrzymamy gotowy program, który może być wielokrotnie używany, a nawet dodawany do istniejących skryptów w celu prezentacji wyników.

#include <iostream>
#include <ostream>
#include "../CImg.h"

using namespace std;
using namespace cimg_library;

unsigned char white[3]={255,255,255},
black[3] = {0,0,0},
red[3] = {255,0,0},
green[3] = {0,255,0},
blue[3] = {0,0,255},
yellow[3] = {255,255,0},
magenta[3] = {255,0,255},
orange[3] = {255,90,0},
middle[3] = {127,127,127}
;

//Tutaj powinna być funkcja cimg_nanies_wyniki2

int main(int argc,char **argv)
{
cimg_usage("Nanies wyniki");

const char* file_i = cimg_option("-i","input.bmp","Input image");
const char* file_o = cimg_option("-o","output.bmp","Output image");
const char* file_m = cimg_option("-m","mask.bmp","Mask image");
const char col = cimg_option("-c",'r',"Color (r-red,g-green,b-blue,y-yellow,m-magenta,o-orange)");
float transp = cimg_option("-t",0.2f,"Transparency");

unsigned char* color=red;
switch (col) {
case 'r': color = red; break;
case 'g': color = green; break;
case 'b': color = blue; break;
case 'y': color = yellow; break;
case 'm': color = magenta;break;
case 'o': color = orange; break;
default: color = red;
}

CImg<unsigned char> image_in(file_i), mask_in(file_m);
mask_in.normalize(255,0);

cimg_nanies_wyniki2(image_in,mask_in,color,transp);
image_in.save(file_o);

return 0;
}


Przykład wywołania:
program -i input.bmp -m mask.bmp -o output.bmp -t 0.15 -c o

środa, 22 października 2008

Wyświetlanie obieków 3D w CImg

0 komentarze
Korzystając z CImg dane przestrzenne (po uprzedniej rekonstrukcji algorytmem marching cubes) można zwizualizować na kilka sposobów. CImg obsługuje następujące tryby:
Na poniższej animacji możemy zobaczyć jak to działa:



Szczegóły najlepiej wyjaśni kod źródłowy:)

wtorek, 21 października 2008

Operowanie na voxelach w CImg

0 komentarze
Operowanie na voxelach, czyli punktach w obrazach przestrzennych, może być równie proste jak operowanie pikselami obrazów 2D. Oczywiście najlepiej jeśli dysponujemy narzędziem, które nas w tym będzie wspierać na każdym kroku. W tym momencie chciałbym ponownie zareklamować bibliotekę CImg, o której pisałem już jakiś czas temu. Ma ona kilka zalet, które sprawią, że nasza praca z danymi przestrzennymi będzie naprawdę przyjemna.
  1. Definicja nowego obrazka pozwala na określenie nie tylko szerokości oraz wysokości, ale również długości dla naszych danych

    CImg<> img(60,60,60); //x,y,z

  2. Część filtrów i funkcji posiada wsparcie dla obrazów przestrzennych. Dla przykładu, aby wykonać dylatację dla takich danych, należy wcześniej zdefiniować element strukturalny w 3D i przekazać go do funkcji.

    CImg<> mask(5,5,5,1,1);img.dilate(mask);

  3. CImg posiada zaimplementowany algorytm marching cubes, dzięki czemu możemy zrekonstruować dane do obiektu 3D.
  4. Korzystając z funkcji display_object3d mamy możliwość wyświetlania obiektu, a następnie przy pomocy myszki możemy go skalować, obracać i przesuwać.
Na poniższym obrazku możemy zobaczyć efekty dylatacji oraz erozji dla wcześniej przygotowanych danych przestrzennych.


Kod źródłowy tego przykładu jest dostępny na stronie Wiki.

sobota, 19 lipca 2008

CImg

0 komentarze
Najlepsze narzędzie w kategorii: Programowanie grafiki rastrowej

Niewiarygodne jest to, jak ta mała biblioteka potrafi ułatwić życie podczas konstruowania nowych algorytmów przetwarzania obrazu. Nie tylko wspiera ona odczyt i zapis dokumentów oraz ich przechowywanie w wygodnej do operowania tablicy dwuwymiarowej, ale potrafi również wyświetlać obrazki i obsługuje zdarzenia myszki i klawiatury.

Zalety:
  • Wyświetlanie obrazków i obsługa zdarzeń myszy
  • Obsługa formatu DICOM (po zainstalowaniu pluginu XMedCon)
  • Wbudowany zestaw filtrów graficznych
  • Możliwość rysowania prostych kształtów (punkt, linia, prostokąt, elipsa, strzałka)
  • Wyświetlanie obiektów w 3D
  • Dobrze dopracowana obiektowość (szablony klas i funkcji, przeładowanie operatorów, konstruktory kopiujące i inne)
  • Prosta w użyciu - wystarczy dołączyć plik cimg.h
  • Pozwala na zamknięcie kodu (licencja CeCILL-C)
Wady:
  • CImg domyślnie potrafi czytać i zapisywać kilka rodzajów plików (m.in. BMP). Obsługa pozostałych formatów graficznych (w tym JPG, GIF, PNG, TIF) możliwa jest dopiero po zainstalowaniu ImageMagicka.
  • Brak przykładów integracji z bibliotekami do tworzenia interfejsu użytkownika (Visual Studio, Borland)
Konkurencja:
Informacje dodatkowe: