Filtr Kuwahara - pomoc w optymalizacji kodu

0

Cześć, zacząłem bawić się w openCV, napisałem filtr Kuwahara, problem w tym, że bardzo długo się ładuje, około 10 sekund w debugu i ok. 1/2 w release w porównaniu z gotowcem z biblioteki.

Tutaj obrazuje działanie filtru: KLIK

Tyle że to jeden obrazek no i nie wyobrażam sobie detekcji w ruchu używając tego filtru na obrazie z kamery.
Jeżeli jest taka możliwość to prosiłbym o pomoc i ew. wytknięcie błędów w celu skróceniu czasu działania.
Jest to mój najdłuższy algorytm, jaki do tej pory napisałem no i nie mam doświadczenia w optymalizacji.

#include"opencv2/opencv.hpp"
#include "opencv2/highgui.hpp"

inline float variance(cv::Mat& mat, float mean)
{
    float var = 0;
    for (uchar &p : cv::Mat_<uchar>(mat))
        var += pow(p - mean, 2);

    return sqrt(var / (mat.size().height * mat.size().width));
}

inline float mean(cv::Mat& mat)
{
    float mean = 0;
    for (uchar &p : cv::Mat_<uchar>(mat))
        mean += static_cast<float>(p);

    return mean / (mat.size().height * mat.size().width);
}

void kuwaharaFiltering(cv::Mat& mat, int m)
{
    if (m % 2 - 1)
        return;

    cv::Size mask(m, m); // tworzenie kwadratowej maski

    cv::Size imgSize = mat.size();
    cv::Size moves(imgSize.width - mask.width, imgSize.height - mask.height); // ilosc ruchów = rozmiar obrazu - rozmiar maski

    cv::Size regionSize = mask / 2;
    regionSize.height += 1, regionSize.width += 1; // rozmiar regionu = (rozmiar maski / 2) + 1

    //[1region][2region]
    //[3region][4region]
    for (int i = 0; i <= moves.height; i++) 
    {
        for (int j = 0; j <= moves.width; j++)
        {
            cv::Mat region1(mat, cv::Rect(
                j, i,   // punkt poczatku wycinania
                regionSize.height, regionSize.width));  // dlugosc/szerokosc wycinania

            float mean1 = mean(region1);
            float var1 = variance(region1, mean1);

            cv::Mat region2(mat, cv::Rect(
                mask.width - regionSize.width + j, i, 
                regionSize.height, regionSize.width)); 

            float mean2 = mean(region2);
            float var2 = variance(region2, mean2);

            cv::Mat region3(mat, cv::Rect(
                j, mask.height - regionSize.height + i,
                regionSize.height, regionSize.width));

            float mean3 = mean(region3);
            float var3 = variance(region3, mean3);

            cv::Mat region4(mat, cv::Rect(
                mask.width - regionSize.width + j, mask.height - regionSize.height + i, 
                regionSize.height, regionSize.width));

            float mean4 = mean(region4);
            float var4 = variance(region4, mean4);

            if (var1 < var2 && var1 < var3 && var1 < var4)
                mat.at<uchar>((mask.height / 2) + i, (mask.height / 2) + j) = mean1;

            else if (var2 < var1 && var2 < var3 && var2 < var4)
                mat.at<uchar>((mask.height / 2) + i, (mask.height / 2) + j) = mean2;

            else if (var3 < var1 && var3 < var2 && var3 < var4)
                mat.at<uchar>((mask.height / 2) + i, (mask.height / 2) + j) = mean3;

            else if(var4 < var1 && var4 < var2 && var4 < var3)
                mat.at<uchar>((mask.height / 2) + i, (mask.height / 2) + j) = mean4;
        }

    }
}

int main()
{   
    cv::Mat greyScaleImg;
    cv::Mat KuwaharaFilter;

    cv::namedWindow("Base");
    cv::namedWindow("Kuwahara Filter");

    KuwaharaFilter = cv::imread("D:/C++/openCV/lena.jpg");
    cv::cvtColor(KuwaharaFilter, greyScaleImg,cv::ColorConversionCodes::COLOR_RGB2GRAY);

    KuwaharaFilter = greyScaleImg.clone();

    kuwaharaFiltering(KuwaharaFilter, 5);

    cv::imshow("Base", greyScaleImg);
    cv::imshow("Kuwahara Filter", KuwaharaFilter);

    cv::waitKey();
}
0

Najpierw wypadaloby zaczac od poprawnosci kodu:

  1. W petli zmieniasz wartosic mat. Jestes pewny ze w nastepnych iteracjach nie wczytujesz wartosci zmienionych zamiast oryginalnych?
  2. To <= jest podejzane - iterujesz moves.height + 1 i moves.width + 1 razy. Tak mialo byc?

Podstawowe optymalizacje:

  1. Przy zalozeniu ze wszystkie wartosci wyjsciowe obliczane sa niezaleznie (a w obecnym kodzie raczej tak nie jest - patrz czesc dotyczaca poprawnosci) to potencjalnie kilka razy mozna przyspieszyc zrownoleglajac obliczenia. Mozna to latwo uzyskac za pomoca #pragma omp parallel for (+ odpowiednie opcje budowania).
  2. Liczac wariancje w kolejnych iteracjach wiekszosc obliczen powtarzasz z poprzedniej iteracji. Jesli zapamietasz i pozniej wykorzystasz to co wczesniej bylo policzone to powinno dac najbardziej zauwazalne przyspieszenie. Niestety to spowoduje ze obliczenia w kazdej iteracji nie beda juz niezalezne, wiec z rownolegloscia moze byc ciezej. Niewykluczone ze optymalnie bedzie zrownloleglac po zewnetrznej petli a wykorzystywac policzone wczesniej wartosci przez wewnetrzna petle.
  3. Wariancje i srednia liczysz osobno, czyli dwa razy przechodzisz przez dane, a spokojnie mozna to policzyc przechodzac przez dane raz. Z reguly powszechnie znany algorytm do liczenia wariancji w jednym przebiegu moze dawac zle wyniki, ale w tym konkretnym przypadku raczej nie bedziesz mial tego problemu.
  4. Taki naprawde to mimo ze nazwales funkcje variance to nie liczysz wariancji tylko odchylenie standardowe. Ale bez wgledu na to czy w opisie algorytmu masz tam pierwiastek czy nie, to nie ma on zadnego znaczenia - skoro wynik obliczen jest wykoszystywany tylko do porownania, to jedynym zastosowaniem tego pierwiastka (oraz pewnie w mniejszytm stopniu dzielenia przez mat.size().height * mat.size().width) jest spowolnienie kodu.

Jest tu jeszcze pare innych rzeczy ktore moga kod przeyspieszyc, ale rownie dobrze mogloby sie okazac w praktyce ze spowolnia - trzeba by napisac i zmierzyc.

I jeszcze kwestia czytelnosci kodu:

  1. O tym juz wyzej wspomnialem - jesli nazywa sie funkcje variance to fajnie by bylo gdyby ona rzeczywiscie liczyla wariancje.
  2. if (m % 2 - 1) - czemu nie if (m % 2 == 0)? Tak wiem, teraz jest modne mierzenie wydajnosci liczba wpisanych znakow (nawet sa tworzone cale jezyki tylko po to zeby nie trzeba bylo srednika na koncu wyrazenia wpisywac), ale potem sie strasznie ciezko taki kod czyta, nie wspominajac o jakichkolwiek modyfikacjach. Z innych rzeczy w tym wyrazeniu ktore sa dalekie od idealu - w przypadku bledu wypadaloby na ten blad zareagowac jakos inaczej niz po prostu nic nie liczyc.
  3. mat.at<uchar>((mask.height / 2) + i, (mask.height / 2) + j) = - czemu 2 razy jest height ? Owszem, w tym kodzie jest to to samo, ale juz sie wprowadza dodatkowa zmienna (ktora w tym kodzie nie jest niezbedna) to wypadaloby z niej korzystac poprawnie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0