Prowadzący zajęcia projektowe: Rafał Petryniak (strona domowa zajęć projektowych).
Spis treści
Głownym zadaniem naszego projektu była kompresja i dekompresja pliku video. Kompresję stosuję się przede wszystkim w celu redukcji kosztów przechowywania i transmisji danych.
Za to zadanie odpowiedzialna była biblioteka OpenCV firmy Intel. Używa ona architektury x86. Została napisana w języku C/C++ i jest jedną z najlepszych darmowych bibliotek do przetwarzania obrazów. Posiada ona ogromne możliwości. Jest ona zoptymalizowana pod kątem operacji w czasie rzeczywistym. Posiada własny system zarządzania okienkami.
W projekcie użyliśmy filtru Sobel'a, który wzmacniał bezpośrednio najbliższe otoczenie piksela przetwarzenego. Filtr ten używany jest głównie do detekcji krawędzi. Filtr Sobel'a stosuję się dla krawędzi poziomych i pionowych. Wylicza się dla nich sumę gradientów, a następnie kierunek gradnientu, aby móc określić kierunek krawędzi. W tym celu została użyta fukncja cvFilter2D z pakietu OpenCv.
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main()
{
CvCapture* capture = cvCreateFileCapture("./video/cat.avi");
cvNamedWindow("stare okno",0);
cvNamedWindow("nowe okno",0);
cvQueryFrame(capture);
double fps = cvGetCaptureProperty(capture,CV_CAP_PROP_FPS);
double frameW = cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH);
double frameH = cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT);
int odstep_miedzy_klatkami = 1000 / fps;
CvVideoWriter *writer = 0;
int isColor = 1;
int fps_wy = fps;
int frameW_wy = frameW;
int frameH_wy = frameH;
writer = cvCreateVideoWriter("./video/out.avi",CV_FOURCC('P','I','M','1'), fps_wy,cvSize(frameW_wy,frameH_wy),isColor);
CvMat* maska = cvCreateMat(3,3,CV_32F);
/*
// nasza maska wyglada nastepujaco
// 0.1 0.1 0.1
// 0.1 0.2 0.1
// 0.1 0.1 0.1
for(int i=0;i<3;i++)
for(intj=0;j<3;j++)
cvSet2D(maska,j,i,cvScalarAll(0.1));
cvSet2D(maska,1,1,cvScalarAll(0.2));
*/
cvSet2D(maska,2,0,cvScalarAll(1.0));
cvSet2D(maska,2,1,cvScalarAll(2.0));
cvSet2D(maska,2,2,cvScalarAll(1.0));
cvSet2D(maska,1,1,cvScalarAll(0.0));
cvSet2D(maska,1,1,cvScalarAll(0.0));
cvSet2D(maska,1,1,cvScalarAll(0.0));
cvSet2D(maska,0,0,cvScalarAll(-1.0));
cvSet2D(maska,0,1,cvScalarAll(-2.0));
cvSet2D(maska,0,2,cvScalarAll(-1.0));
double all_frames = cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_COUNT);
while (true)
{
IplImage* ramka = cvQueryFrame(capture);
if (ramka != 0){
cvShowImage("stare okno", ramka);
cvSaveImage("out_without_filter.bmp",ramka);
cvFilter2D(ramka,ramka,maska);
cvSaveImage("out_with_filter.bmp",ramka);
//cvWriteFrame(writer,ramka);
}
else{
//system("PAUSE");
break;
}
int c = cvWaitKey(odstep_miedzy_klatkami);
if (c == 'k')
break;
cvShowImage("nowe okno", ramka);
cvWriteFrame(writer,ramka);
}
/*
CvCapture* capture_new = cvCreateFileCapture("out.avi");
cvQueryFrame(capture_new);
while (true)
{
IplImage* ramka_new = cvQueryFrame(capture_new);
if (ramka_new != 0)
cvShowImage("nowe okno", ramka_new);
else{
system("PAUSE");
break;
}
int d = cvWaitKey(odstep_miedzy_klatkami);
if (d == 'z')
break;
}
*/
cvReleaseMat(&maska);
cvReleaseVideoWriter(&writer);
cvReleaseCapture(&capture);
//cvReleaseCapture(&capture_new);
cvDestroyAllWindows();
return 1;
}Tworzenie okna dla starego pliku video. | |
Tworzenie okna dla nowego pliku video. | |
Odczytanie pierwszej ramki. | |
Odczytnie ilosci ramek na sekunde. | |
Wczytanie odstepu miedzy klatkami. | |
Tworzymy macierz 3x3. | |
Zastosowanie zwyklego filtru wyostrzajacego. | |
Zastosowanie filtru Sobel. Ustawienie macierzy. | |
Wczytanie ilosci ramek w calym filmie. | |
Zapisanie obrazu przed zastosowaniem filtru. | |
Zastosowanie maski wyostrzajacej. | |
Zapisanie obrazu po zastosowaniu filtru. | |
Inicjalizacja przechwytu z nowego pliku. | |
Odczytanie pierwszej ramki nowego pliku, pobranie nastepnej oraz wyswietlenie. | |
Zwolnienie niepotrzebnych zasobow. |
Naszym celem było stworzenie algorytmu przetwarzania równoległego video za pomocą jednostek wektorowych (SPE). Rozkładając obliczenia na poszczególne rdzenie, wykorzystaliśmy inną niż w poprzednim algorytmie bibliotekę - CImg.
W projekcie zostało użyte proste przetwarzanie w postaci tworzenia negatywu. Program działa na plikach mpeg. Jest przystosowany do optymalnego przetwarzania plików video o dowolnych wymiarach.
BIBLIOTEKA CImg:
ZALETY:
Wbudowany zestaw filtrów graficznych
Możliwość rysowania prostych kształtów
Wyświetlanie obrazków i obsługa zdarzeń myszy
Prosta w użyciu - wystarczy dołączyć plik cimg.h
WADY:
Brak przykładów integracji z bibliotekami do tworzenia interfejsu użytkownika
CImg domyślnie potrafi czytać i zapisywać kilka rodzajów plików, pozostałe formaty graficzne obsługiwane są po zainstalowaniu dodtakowych komponentów
#include <cstdio> #include <libspe2.h> #include <pthread.h> #include "CImg.h" #include "context.h" using namespace cimg_library; using namespace std; bool file_exists(const char * filename);void print_video_info(const CImgList<unsigned char> &video); spe_program_handle_t* spu; void *spe_handler(void *arg)
{ spe_context_ptr_t spe_ctx; context *data = (context *)arg; unsigned int entry=SPE_DEFAULT_ENTRY;; spe_ctx = spe_context_create(0, NULL); spe_program_load (spe_ctx, spu); spe_context_run(spe_ctx, &entry,0,data,NULL,NULL); spe_context_destroy(spe_ctx); pthread_exit(NULL); } int main(int argc, char** argv) { if(argc == 1) { printf("Nie podales nazwy pliku wejsciowego!\n%s nazwa_filmu_wej.mpg nazwa_filmu_wyj.mpg\n",argv[0]); exit(1); } if(argc == 2) { printf("Nie podales nazwy pliku wyjsciowego!\n%s nazwa_filmu_wej.mpg nazwa_filmu_wyj.mpg\n",argv[0]); exit(1); } if(!file_exists(argv[1])) { printf("Nie mozna otworzyc pliku %s\n",argv[1]); exit(1); } CImgList<unsigned char> video;
video.load_ffmpeg(argv[1]); print_video_info(video); int height = video[0].height; int width = video[0].width; int num_spe = spe_cpu_info_get(SPE_COUNT_USABLE_SPES, -1);
int spu_part_size = (width * height * 3) / num_spe;
int rest_spu_part_size = (width * height * 3) % num_spe; pthread_t *pthreads = new pthread_t[num_spe];
context *ctxs = new context[num_spe]; spu = spe_image_open("video-spu");
printf("Przetwarzanie przez %d jednostek SPU\n",num_spe); printf("Prosze czekac(moze to potrwac kilka minut)...\n"); printf("---------------------------------------------\n");
int old_progress = 0; for(int j=0; j<video.size; j++) { unsigned char *in_data = video[j].ptr();
for(int i=0;i<num_spe;i++) { ctxs[i].num = spu_part_size;
if(i == num_spe-1) ctxs[i].num += rest_spu_part_size; ctxs[i].frame_addr = (unsigned long long int)(in_data)+ctxs[i].num*i;
pthread_create(&pthreads[i], NULL, &spe_handler, &ctxs[i]); } for (int i=0; i<num_spe; i++)
pthread_join (pthreads[i], NULL); } printf("Zakonczono przetwarzanie! Trwa zapis pliku wideo: %s\n", argv[2]); video.save_ffmpeg(argv[2]);
printf("Zapis zakonczony powodzeniem!\n"); delete[] pthreads; delete[] ctxs; return (0); }
bool file_exists(const char * filename) { if (FILE * file = fopen(filename, "r")) { fclose(file); return true; } return false; } void print_video_info(const CImgList<unsigned char> &video)
{ printf("Przetwarzanie video\n"); printf("----------------------------------------------------------------\n"); printf("INFO:\n"); printf("szerokosc: %d\n", video[0].width); printf("wysokosc: %d\n", video[0].height); printf("ilosc klatek: %d\n\n", video.size); }
Zapowiedzi funkcji pomocniczych i uchwyt do programu SPU. | |
Funkcja wykonywana przez pojedynczy watek(uruchomienie jednostki SPU, wyslanie danych i zakonczenie watku). | |
Zaladowanie video. | |
Liczba dostepnych do przetwarzania jednostek SPU. | |
Wielkosc czesci pikseli do przetworzenia dla kazdej jednostki, oraz liczba pikseli nadmiarowych. | |
Kontenery na watki oraz na konteksty wywolania watkow(dane do nich przesylane). | |
Dynamiczne zaladowanie programu dla SPU. | |
Wskaznik postepu, 40 znakow. | |
Adres poczatku danych kazdej klatki. | |
Uwzglednienie nierownomiernego podzialu, jesli wystapi ostatnia jednoskta SPU wykonuje nadmiar. | |
Wyliczenie odresu poczatkowego piksela dla kazdej jednostki SPU. | |
Oczekiwanie na zakonczenie wątków SPE. | |
Zapis przetworzonego video. | |
Funkcje pomocnicze. 1. Funkcja sprawdza czy plik istnieje. | |
2. Funkcja wyswietlajaca informacje o pliku wideo. |
#include <cstdlib> #include <cstdio> #include <spu_mfcio.h> #include "context.h" #define MAX_CHUNK_SIZE 4096unsigned char local_pixel_data[MAX_CHUNK_SIZE] __attribute__ ((aligned (128)));
context context_data;
void negatyw(unsigned char* pixel_data, unsigned int size)
{ vector unsigned char *pixels; vector unsigned char maxPixel = (vector unsigned char) {255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255}; pixels = (vector unsigned char*) pixel_data; int vectorchunk = (size / 16);
for (int i = 0; i < vectorchunk; i++) { pixels[i] = maxPixel - pixels[i]; } int rest = size%16;
int rest_position = vectorchunk*16; for (int i = 0; i < rest; i++) { pixel_data[rest_position+i] = 255 - pixel_data[rest_position+i]; } } int main(unsigned long long speid __attribute__((unused)), unsigned long long argp) { unsigned long long frame_addr;
unsigned int chunk_size; unsigned int tag = mfc_tag_reserve();
if (tag == MFC_TAG_INVALID) { printf ("BLAD SPU! \n Nie mozna przydzielic tagu dla SPU.\n"); return 1; } mfc_get (&context_data, argp, sizeof (context_data), tag, 0, 0);
mfc_write_tag_mask (1 << tag);
mfc_read_tag_status_all (); int num_chunks = context_data.num/MAX_CHUNK_SIZE;
int last_chunk_size = context_data.num%MAX_CHUNK_SIZE; if(last_chunk_size != 0) num_chunks++; for (int i = 0; i < num_chunks; i++)
{ if(last_chunk_size != 0 && i == (num_chunks-1))
chunk_size = last_chunk_size; else chunk_size = MAX_CHUNK_SIZE; frame_addr = context_data.frame_addr + (i* MAX_CHUNK_SIZE * sizeof(unsigned char)); mfc_get (local_pixel_data, frame_addr, chunk_size * sizeof(unsigned char), tag, 0, 0);
mfc_write_tag_mask (1 << tag); mfc_read_tag_status_all (); negatyw (local_pixel_data, chunk_size);
mfc_put (local_pixel_data, frame_addr, chunk_size * sizeof(unsigned char), tag, 0, 0); mfc_write_tag_mask (1 << tag); mfc_read_tag_status_all (); } return 0; }
Maksymalny rozmiar przetwarzanego kawałka obrazu. | |
Lokalny bufor przechowujacy otrzymywane i wysylane przez SPE dane (piksele). | |
Struktura przechowujaca informacje potrzebne do przetworzenia, otrzymane przez jednoske SPU. | |
Funkcja przetwarzajaca size pikseli wskazywanych przez pixel_data. Aby przyspieszyc dzialanie, zastosowane mozliwe w tym wypadku przetwarzanie wektorowe grupujace przetwarzane piksele. | |
Ilosc iteracji podczas wektorowego przetwarzania. | |
Jesli zostana jakies nieprzetworzone piksele, przetwarzamy je juz w normalnej petli: rest okresla liczbe pikseli pozostalych do przetworzenia, rest_position pozycje pierwszego piksela do przetworzenia. | |
frame_addr: Zmienna pomocnicza wykorzystywana w petli glownej, zawierajaca wyliczony adres pikseli do odczytania z lokalnej pamieci i wyslania spowrotem do PPU. chunk_size: Zmienna pomocnicza wykorzystywana w petli glownej, zawierajaca wielkosc aktualnie przetwarzanego kawalka (wprowadzona by poprawnie obsluzyc nierownomierny podzial danych na kawałki). | |
Przydzielenie MFC tag'u dla naszego SPE. | |
Odczytanie z DMA informacji dotyczacych przetwarzanych pikseli przekazanych z SPU. | |
Oczekiwanie na zakonczenie przesylania danych. | |
Obliczenie wielkosci pojedynczego kawałka danych do przetworzenia oraz ostatniego kawalka do przetworzenia (jesli podzial jest nierownomierny). | |
Petla glowna przetwarzania klatki przez SPE. Dane dzielone są na kawałki (chunks), przetwarzane i nastepnie kopiowane z pamieci lokalnej SPE do pamieci systemowej PPU. Podzielenie na kawalki i buforowanie zapewnia bardziej plynne przetwarzanie danych. | |
Obliczenie adresu aktualnie przetwarzanej porcji danych. | |
Przeslanie przetworzonego kawałka dnych z pamieci lokalnej SPE do pamieci systemowej PPU. | |
1. Przetwarzanie otrzymanych danych. 2. Przeslanie przetworzonego kawałka dnych z pamieci lokalnej SPE do pamieci systemowej PPU. 3. Oczekiwanie na zakonczenie przesylania danych. |