22 августа 2011 в 21:03

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

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). Так же в дальнейшем мне бы хотелось бы написать статью, похожей на эту, но уже про работу с видео и использованием уже других библиотек.

У кого возникли вопросы или заметили ошибки, или просто хотите похвалить автора, пишите, обязательно отвечу.
Ашрапов Инсаф @4knowledge
карма
5,0
рейтинг 0,0
Базы данных, Teradata
Самое читаемое Разработка

Комментарии (14)

  • +1
    На Хабре есть блог «Обработка изображений». Уровень статьи низкий, но с почином вас!
    • +2
      Последнее можно было и не писать, но спасибо
      • +4
        Первый курс такой первый :) По сути, Вы воспроизвели лекцию (семинар?), по обработке изображений. Как одна лекция из курса — вполне, только орфографические ошибки стоит вычистить, форматирование — вполне, красиво.

        А вот смысл статьи, правда, для меня — загадка: зачем это? Какая практическая задача решилась (ну, кроме сдачи задания в ВУЗе)?
  • +1
    Эмм… Я не понял. «Возьмём открытую библиотеку, возьмём известную формулу и напишем статью?». В чем сложность этой задачи или в чём новизна? На OpenCV куда более красивые вещи можно за пол часа сделать. Лучше спрячьте статью в черновики…
    • +1
      Тем более есть замечательный цикл статей по OpenCV
      • –1
        Главное в этой статье было находение ssim и psnr. Там, я уверен, вы этого не найдете.
        • 0
          Ниже вам сказали что в OpenCV есть множество инструментов, позволяющих решить эту же задачу проще. А если бы вы внимательнее посмотрели ссылку, которую я дал, то натолкнулись бы в разделе «Разное» на целых две статьи решающих проблему сравнения изображений. В том числе более интересными методами, такими как перцептивный хэш.
          И да, называть логарифм среднеквадратичного отклонения картинок друг от друга отношением «Сигнал/шум» это несколько неправильно.
      • +1
        Ну вот и польза статьи — ваш комментарий (:
        Я как раз начинаю изучать OpenCV, и постоянно натыкаюсь на то,
        что реализую уже существующие вещи.

        Спасибо за ссылку.
        Не подскажите что-нить подобное на англ?
        • 0
          Когда мы что-то искали в OpenCV, то кроме манов(которые там весьма подробные) пользовались книжкой Learning OpenCV Computer Vision with the OpenCV Library. В итоге то, что нам было интересно мы, пожалуй, там не нашли. Но инфы там очень много и местами полезной.
          • 0
            Авторы — Gary Bradski and Adrian Kaehler
          • 0
            Книжка хорошая, мы ее сразу купили, но там много всего и искать конкретные вещи не очень получается.
            Впрочем, за ответ благодарю.
  • 0
    Статья предназначалась тем кто с этим еще не сталкивался, если вы уже знаете это, отлично. Мне же, в свое время, это все сэкономило бы много времени. Тем более кому-то статья пригодиться, пусть же они имеют к ней доступ.
  • 0
    Я точно помню что в OpenCV есть функция для подсчета среднего — cvAvg. Чтобы посчитать PSNR достаточно трех имеющихсяв библиотека функций функций: cvSub, cvPow, cvSum. Аналогично для SSIM. Не изобретайте велосипед, а изучайте матчасть.
    • 0
      Спасибо, буду знать.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.