Pull to refresh

PSNR и SSIM или как работать с изображениями под С

Reading time 4 min
Views 29K
imageВ данной статье я коснусь базовых принципов, как работать с изображениями. Для этого я выбрал библиотеку OpenCV. Она распространяется бесплатно, так что скачать ее не составит труда.
Когда мне на учебе дали задание написать две метрики для оценки различия двух картинок, в частности качества видоизмененной от исходной, меня это конечно все это немного смутило. Знания в программировании были, мягко говоря, не очень большими, как-никак был только на первом курсе. Благо, какую библиотеку выбрать сказали заранее, так что с этим труда не возникло. А вот как ее использовать это было уже на порядок сложнее, все, что я в основном смог нарыть в интернете, было на английском, хоть я его и знаю на уровне, что могу читать тех. литературу, вследствие огромности самой библиотеки, подходило мало. Отлично, что удалось, какие функции и как использовать, я смог потом уточнить у преподавателя. А требовалось только понять как обращаться к самой картинке, в частности к отдельным пикселям изображения. Кого заинтересовало, добро пожаловать под кат.

PSNR/SSIM

Начнем с того, что же это такое PSNR и SSIM. Как нам подсказывает википедия. PSNR — peak signal-to-noise ratio, наиболее часто используется для измерения уровня искажений при сжатии изображений. А найдем мы ее так. При этом все будет зависеть от битности изображения.

image

Где MSE среднеквадратичное отклонение

image

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

image

Тут image(х) среднее значение для первой картинки, image(y) это для второй, image(x) среднеквадратичное отклонение для первой картинки, и соотвественно image(y) для второй, image(x,y) это уже ковариация. Напомню она находиться image(x,y) = image(x,y) — image(x)*image(y). Продолжим С1 и С2, поправочные коэффициенты, которые нужны нам вследствие малости знаменателя. Причем они равны квадрату числа, равное количеству цветов, соответствующему данной битности изображения, умноженной на и 0.01 и 0.03 соответственно.

Код

Каюсь, стиль программирования у меня наверняка не очень хорош, но не судите меня строго, у меня ведь не так много опыта и обещаю все время улучшать его. В рамках данной статьи я ограничусь разбором только SSIM метрики. Для начала надо будет иметь под рукой Visual Studio`у. Тут мне достаточно просто удалось понять, как же добавить в проект изначально выбранную библиотеку. Нужно добавить заголовочные файлы, ну и сами файлы в ново созданный проект. Более подробно это написано тут.
Для объяснения моего написанного кода, я сделаю ставку на её комментирование по ходу. Код не слишком длинен, так что его можно будет понять. Ну что ж, приступим.

Указатели на картинки мы получим так.
IplImage* img1 = cvvLoadImage("before.bmp");
IplImage* img2 = cvvLoadImage("after.bmp");

Битность можно определить
img->depth
То есть достаточно просто.

Для нахождения искомых величин я написал парочку своих функций. Для нахождения image(х), то есть среднего значения. По сути с картинкой работаем как с двумерным массивом. Еще тут полученное значение надо будет разделить на размер картинки, найденное так:

(img->width)*(img->height)


double avg(IplImage* img){    
    unsigned char* ptr;
    int x,y,b=0,g=0,r=0,col=0;

    for(x=0;x<=img->width;x++){
            for(y=0;y<=img->height;y++){
                ptr=(uchar*)(img->imageData+y*img->widthStep);   //подсчитаем среднее значение для каждого компонента цветов
                b=ptr[3*x];
                g=ptr[3*x+1]; // вот так мы как раз обращаемся к различным компонентам цветов
                r=ptr[3*x+2]; // b - blue, r - red, g - green
                col+=b+g+r;
    }}
    return col/3.00; 
}


Среднеквадратичное отклонение так, тут нам придется помимо всего прочего передавать и ранее полученные средние значения.

double var(IplImage* img, double mu){       //variance
    unsigned char* ptr;
    int x,y,b=0,g=0,r=0;
    double col=0;

    for(x=0;x<=img->width;x++){
            for(y=0;y<=img->height;y++){
                ptr=(uchar*)(img->imageData+y*img->widthStep);

                b=ptr[3*x];
                col+=(abs(1.0*b-mu))*(abs(1.0*b-mu));
                g=ptr[3*x+1];
                col+=(abs(1.0*g-mu))*(abs(1.0*g-mu));
                r=ptr[3*x+2];
                col+=(abs(1.0*r-mu))*(abs(1.0*r-mu));
   }}
    return col;
}


А ковариацию вот так вот:

double cov(IplImage* img1,IplImage* img2,double mu1,double mu2){   //covariance
    unsigned char* ptr;
    int x,y,b1=0,g1=0,r1=0,b2=0,g2=0,r2=0;
    double col=0;

    for(x=0;x<=img1->width;x++){
            for(y=0;y<=img1->height;y++){
                ptr=(uchar*)(img1->imageData+y*img1->widthStep);
                b1=ptr[3*x];
                g1=ptr[3*x+1];
                r1=ptr[3*x+2];

                ptr=(uchar*)(img2->imageData+y*img2->widthStep);
                b2=ptr[3*x];
                g2=ptr[3*x+1];
                r2=ptr[3*x+2];

                col+=(b1-mu1)*(b2-mu2)+(g1-mu1)*(g2-mu2)+(r1-mu2)*(r2-mu2);

   }}

    return col;
}


По сути нам только остается подставить полученные значения в выше стоящую формулу. Так же не надо забывать освобождать память.

    cvReleaseImage(&img1);
    cvReleaseImage(&img2);


P.S.
Скачать саму библиотеку вы сможете тут. Много чего интересного про OpenCV здесь (спасибо ZlodeiBaal). Так же в дальнейшем мне бы хотелось бы написать статью, похожей на эту, но уже про работу с видео и использованием уже других библиотек.

У кого возникли вопросы или заметили ошибки, или просто хотите похвалить автора, пишите, обязательно отвечу.
Tags:
Hubs:
+14
Comments 14
Comments Comments 14

Articles