Перейти к содержимому
Compvision.ru
David

Сравнение изображений через Хэш

Recommended Posts

Здравствуйте уважаемые форумчане! Недавно прочитал интересную статью на habrahabr.ru о сравнении изображений через хэш, также ознакомился с кодом на robocraft.ru. Насколько я понял, чем меньше число Хэмминга, тем больше сходство изображения с эталоном.

Хотелось бы опробовать данный метод для распознавания отделов позвоночника. Я планирую сделать 3 папки - для снимков шейного, поясничного и грудного отделов, организовать сравнение всех изображений в каждой папке с 3 шаблонами отделов и сохранить результаты сравнения в текстовый файл (всего 173 изображения).

Подскажите пожалуйста как это организовать в C++, вообще стоит ли использовать данный метод и что подправить в коде предобработки, чтобы изображение с законченной предварительной обработкой dilateImg можно было использовать и дальше в функциях расчета хэша и расстояния Хэмминга?

int main( int argc, char** argv )   

{  

    IplImage *src = 0, *dst = 0, *img = 0,  *im_bw = 0, *gray = 0,  *src1 = 0, *dst1 = 0, *newImg = NULL, *dilateImg = NULL, * erodeImg = NULL,*image=0;


   src = cvLoadImage("Image1.jpg"); // **градации серого при загрузке     


    IplImage* resizedImageLinear = cvCreateImage(cvSize(512, 512),IPL_DEPTH_8U,src->nChannels);//**линейная интерполяция

	cvResize(src,resizedImageLinear, CV_INTER_LINEAR); //, изменение размера


  //клонирование изображения

    dst = cvCloneImage(resizedImageLinear); 

	// Image adjust  

    if( ImageAdjust( resizedImageLinear, dst, 0, 1, 0, 1, 1)!=0) return -1;  


        // медианная фильтрация

        cvSmooth(resizedImageLinear, dst, CV_MEDIAN, 5, 5);


		// картинка для хранения изображения в градациях серого

        gray = cvCreateImage(cvGetSize(dst), dst->depth, 1);


        // преобразуем картинку в градации серого

	    cvConvertImage(dst, gray, CV_BGR2GRAY);


        dst = cvCreateImage( cvGetSize(gray), IPL_DEPTH_8U, 1);


		// выполняем пороговое преобразование 

        cvThreshold(gray, dst, 50, 255, CV_THRESH_BINARY);


		//imfill(holes) - заполнение отверстий


		src1 = cvCloneImage(dst);

		dst1 = cvCreateImage(cvGetSize(dst), IPL_DEPTH_8U, 1);


	    imfill(src1,dst1);


		erodeImg=cvCloneImage( dst1 );


		//эрозия изображения

		cvErode(dst1,erodeImg,0,3); 

			cvSaveImage("702.jpg", erodeImg);


		image = cvCloneImage(erodeImg);


		// удаление мелких объектов

		bwareaopen_(image,200);


	// дилитация

	dilateImg=cvCloneImage(image);

}

Расчет хэш и расстояния Хэмминга:
// рассчитать хеш картинки

__int64 calcImageHash(IplImage* image, bool show_results=false);

// рассчёт расстояния Хэмминга

__int64 calcHammingDistance(__int64 x, __int64 y);


int main(int argc, char* argv[])

{

        IplImage *object=0, *image=0;


        char obj_name[] = "cat2.jpg";

        char img_name[] = "cat.jpg";


        // имя объекта задаётся первым параметром

        char* object_filename = argc >= 2 ? argv[1] : obj_name;

        // имя картинки задаётся вторым параметром

        char* image_filename = argc >= 3 ? argv[2] : img_name;


        // получаем картинку

        object = cvLoadImage(object_filename, 1);

        image = cvLoadImage(image_filename, 1);


        printf("[i] object: %s\n", object_filename);

        printf("[i] image: %s\n", image_filename);

        if(!object){

                printf("[!] Error: cant load object image: %s\n", object_filename);

                return -1;

        }

        if(!image){

                printf("[!] Error: cant load test image: %s\n", image_filename);

                return -1;

        }


        // покажем изображение

        cvNamedWindow( "object");

        cvShowImage( "object", object );

        cvNamedWindow( "image");

        cvShowImage( "image", image );


        // построим хэш

        __int64 hashO = calcImageHash(object, true);

        //cvWaitKey(0);

        __int64 hashI = calcImageHash(image, false);


        // рассчитаем расстояние Хэмминга

        __int64 dist = calcHammingDistance(hashO, hashI);


        printf("[i] Hamming distance: %I64d \n", dist);


        // ждём нажатия клавиши

        cvWaitKey(0);


        // освобождаем ресурсы

        cvReleaseImage(&object);

        cvReleaseImage(&image);


        // удаляем окна

        cvDestroyAllWindows();

        return 0;

}


// рассчитать хеш картинки

__int64 calcImageHash(IplImage* src, bool show_results)

{

        if(!src){

                return 0;

        }


        IplImage *res=0, *gray=0, *bin =0;


        res = cvCreateImage( cvSize(8, 8), src->depth, src->nChannels);

        gray = cvCreateImage( cvSize(8, 8), IPL_DEPTH_8U, 1);

        bin = cvCreateImage( cvSize(8, 8), IPL_DEPTH_8U, 1);


        // уменьшаем картинку

        cvResize(src, res);

        // переводим в градации серого

        cvCvtColor(res, gray, CV_BGR2GRAY);

        // вычисляем среднее

        CvScalar average = cvAvg(gray);

        printf("[i] average: %.2f \n", average.val[0]);

        // получим бинарное изображение относительно среднего

        // для этого воспользуемся пороговым преобразованием

        cvThreshold(gray, bin, average.val[0], 255, CV_THRESH_BINARY);


        // построим хэш

        __int64 hash = 0;


        int i=0;

        // пробегаемся по всем пикселям изображения

        for( int y=0; y<bin->height; y++ ) {

                uchar* ptr = (uchar*) (bin->imageData + y * bin->widthStep);

                for( int x=0; x<bin->width; x++ ) {

                        // 1 канал

                        if(ptr[x]){

                                // hash |= 1<<i;  // warning C4334: '<<' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?)

                                hash |= 1i64<<i; 

                        }

                        i++;

                }

        }


        printf("[i] hash: %I64X \n", hash);


        if(show_results){

                // увеличенные картинки для отображения результатов

                IplImage* dst3 = cvCreateImage( cvSize(128, 128), IPL_DEPTH_8U, 3);

                IplImage* dst1 = cvCreateImage( cvSize(128, 128), IPL_DEPTH_8U, 1);


                // показываем картинки

                cvNamedWindow( "64");

                cvResize(res, dst3, CV_INTER_NN);

                cvShowImage( "64", dst3 );

                cvNamedWindow( "gray");

                cvResize(gray, dst1, CV_INTER_NN);

                cvShowImage( "gray", dst1 );

                cvNamedWindow( "bin");

                cvResize(bin, dst1, CV_INTER_NN);

                cvShowImage( "bin", dst1 );


                cvReleaseImage(&dst3);

                cvReleaseImage(&dst1);

        }


        // освобождаем ресурсы

        cvReleaseImage(&res);

        cvReleaseImage(&gray);

        cvReleaseImage(&bin);


        return hash;

}


// рассчёт расстояния Хэмминга между двумя хэшами

// http://en.wikipedia.org/wiki/Hamming_distance

// http://ru.wikipedia.org/wiki/Расстояние_Хэмминга

//

__int64 calcHammingDistance(__int64 x, __int64 y)

{

        __int64 dist = 0, val = x ^ y;


        // Count the number of set bits

        while(val)

        {

                ++dist; 

                val &= val - 1;

        }


        return dist;

}

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Думаю, что лучше Вам работать с контурами, или с уменьшенными изображениями и нейронной сетью или мультиклассовой SVM.

Как я понял, использовать хэш имеет смысл, если изображения имеют больше двух градаций яркости.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

У меня изображения после предварительной обработки получаются бинаризованными. Проверил снимков 10, в принципе 8 из них верно идентифицируются, при правильном подборе порога бинаризации (treshold). Подскажите пожалуйста как данные из dilateImg передать дальше на хэш-обработку (уже все перепробовал)?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Надо конвертировать ее в трехканальное изображение.

        object = cvLoadImage(object_filename, 1);
image = cvLoadImage(image_filename, 1);[/code]

В функции расчета хэша загружают цветное изображение (может быть и серое, но трехканальное), а dilateImg одноканальное, вот и несостыковка.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Smorodov спасибо,теперь разобрался и все работает! А как можно сделать массивы из нескольких изображений? мне это надо чтобы сравнивать исходное изображение с каждым из 3-ех шаблонов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

vector<Mat> myMatArray;

дальше работа как с std-шными векторами.

Например передача параметра:

void myFunc(vector<Mat>& V)
{
// Заполнение
for(.....)
{
// Матрицу создавать тут.
Mat v=...
V.push_back(v);
}

// Обращение
Mat t=V[5];

}[/code]

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

на самом деле сейчас заметил, что после перевода изображения в трехканальное, оно полностью потерялось, т.е. сравнение шаблонов происходил с черным цветом. как можно перевести без потери данных?

image = cvCreateImage( cvGetSize(dilateImg), dilateImg->depth, 3);

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Так это при помощи cvtColor делается с аргументом содержащим GRAY2BGR.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

подскажите пожалуйста, что я не так делаю, вот код:

newImg=cvCloneImage(dilateImg);

cvZero(image);

newImg=cvCreateImage( cvGetSize(dilateImg), dilateImg->depth, 3);

cvCvtColor(newImg, image, CV_GRAY2BGR);

cvShowImage( "dilate", image );

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

// Эта строчка создает точную копию dilateImg

newImg=cvCloneImage(dilateImg);

// Эта заполняет нулями image (оно где то создавалось?)

cvZero(image);

// Эта строчка создает новое (зачем?) изображение. Оно уже создано клонированием.

newImg=cvCreateImage( cvGetSize(dilateImg), dilateImg->depth, 3);

// Эта строчка из серого newImg делает цветное image (зачем его было обнулять?)

cvCvtColor(newImg, image, CV_GRAY2BGR);

// Эта выводит изображение

cvShowImage( "dilate", image );

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

получается, что мне нужно оставить только эти строчки:

newImg=cvCloneImage(dilateImg);

cvCvtColor(newImg, image, CV_GRAY2BGR);

а dilateImg итак трехканальное изобр., вроде все верно , но не работает, выдает в консоль:

post-6404-0-07392400-1370891042_thumb.pn

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

так image то надо создать.

И зачем тогда конвертировать, если newImg и так трехканальное?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

        image = cvCreateImage( cvGetSize(dilateImg), dilateImg->depth, 3); 


                newImg=cvCloneImage(dilateImg);


                cvCvtColor(newImg, image, CV_GRAY2BGR);


                cvShowImage( "dilate", image );

                

вот так все работает, если убрать конвертацию, то снова получится черное изображение.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Из твоего кода вообще непонятно что и зачем делается. Они зачем-то пачками создаются, клонируются и пересоздаются. Попробуй разобраться, что тебе надо и в какой последовательности.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Nuzhny, да есть недочеты в плане лишних действий, я это учту при корректировке, а так все работает. Вот я организовал распознавание по 1 изображению:



	IplImage *object[3];



		char *array_1[3]={ 

                "C:\\Recognition\\Recognition\\Шаблоны1\\SH1.jpg", // загрузка шаблонов

                "C:\\Recognition\\Recognition\\Шаблоны1\\poyas.jpg", 

                "C:\\Recognition\\Recognition\\Шаблоны1\\grud.jpg", 

        }; 



			char *array_2[3]={ 

						"шейный отдел", // названия шаблонов


						"поясничный отдел",


						"грудной отдел",

        }; 




			char*file="C:\\Recognition\\Recognition\\Грудной\\Разумов - 1971 Г  195327.jpg"; // изображение, которое надо распознать



for(i=0;i<3;i++) object[i]=0; 


for(i=0;i<3;i++) 

        { 

                object[i] = cvLoadImage( array_1[i], 1 );


image = cvLoadImage(file,1);

         ...

подскажите пожалуйста, можно ли обойтись без векторов при обработке большого кол-ва картинок? К примеру считать все имена изображений в определенной папке,записать их в массив (char*) и запустить цикл с поочередным считыванием изображений (т.е. путь где лежит картинка брать из массива)?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Я люблю векторы :)

#include <opencv2/opencv.hpp>
#include <vector>
#include <list>
#include <iostream>
#include <windows.h>
using namespace cv;
using namespace std;
//----------------------------------------------------------------------
// Получение списка файлов в заданной директории
//----------------------------------------------------------------------
vector<string> listFilesInDirectory(string directoryName)
{
WIN32_FIND_DATA FindFileData;
HANDLE hFind = FindFirstFile(directoryName.c_str(), &FindFileData);
vector<string> listFileNames;
listFileNames.push_back(FindFileData.cFileName);
while (FindNextFile(hFind, &FindFileData))
{
listFileNames.push_back(FindFileData.cFileName);
}
return listFileNames;
}

void main(void)
{
...
string DirName="..\\..\\faces\\";
string ExtFilter="*.png";
vector<string> lst=listFilesInDirectory(DirName+ExtFilter);

for(int i=0;i<lst.size();i++)
{
img=imread(DirName+lst[i],0);
if(!img.empty())
{
// Делаем что то полезное
}
}
....

}
[/code]

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Smorodov, у меня получилось считать путь и названия к изображениям, теперь я хочу поочередно их загружать через cvLoadImage, но эта ф-ия не поддерживает работу со строковыми данными (только char),а в char список имен lst не записывается.


	int i,k, min,index=0,schet=0;


			int array_rec[3];


		string array_str[200];//сохранение директории и имен файлов

        string DirName="C:\\Recognition\\Recognition\\Шейный\\";

        string ExtFilter="*.jpg";

        vector<string> lst=listFilesInDirectory(DirName+ExtFilter);



	IplImage *object[3];



		char *array_1[3]={ 

                "C:\\Recognition\\Recognition\\Шаблоны1\\shei70.jpg", // загрузка шаблонов

                "C:\\Recognition\\Recognition\\Шаблоны1\\poyas.jpg", 

                "C:\\Recognition\\Recognition\\Шаблоны1\\grud.jpg", 

        }; 



			char *array_2[3]={ 

						"шейный отдел", // названия шаблонов


						"поясничный отдел",


						"грудной отдел",

        }; 




			//char*file="C:\\Recognition\\Recognition\\Шейный\\Лосева -1970 Ш 90243.bmp.jpg"; // изображение, которое надо распознать


        for(int k=0;k<lst.size();k++)

        {

                imread(DirName+lst[k],0);

                array_str[k]=DirName+lst[k];// полный путь к изображению и его название

				printf(" %s\n",array_str[k]);

				schet++;




for(i=0;i<3;i++) object[i]=0; 


for(i=0;i<3;i++) 

        { 

                object[i] = cvLoadImage( array_1[i], 1 );


image = cvLoadImage(...,1); // здесь загружаются изображения для идентификации


   ...



Как можно преобразовать из строки в char или считать из *.txt построчно в char (у меня все пути/названия сохраняются в файл вместо вывода в консоль)? примеры на msdn не помогли

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

char* из string получить просто:

char* ch=str.c_str();

string из char* тоже

string str=string(ch); или просто str=ch;

PS: Русские буквы в путях добавят Вам проблем.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Отправлено Сегодня, 12:32

char* из string получить просто:

char* ch=str.c_str();

string из char* тоже

string str=string(ch); или просто str=ch;

PS: Русские буквы в путях добавят Вам проблем

Я пробовал так сделать, мне выдает:

	11	IntelliSense: значение типа "const char *" нельзя присвоить сущности типа "char *"	c:\recognition\recognition\recognition.cpp	249

к примеру если так конвертировать путь к файлу
char*ch=DirName.c_str();

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Очень странно смотрится смесь CLI и С без плюсов.

Самым лучшим вариантом было бы перейти на C++ интерфейс, иначе программа заполнится большим количеством костылей.

CLI не использую, но могу предложить попробовать либо преобразовать const char* в char*, так (char*)var или так static_cast<char*>(var), второй вариант правильнее.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
CLI не использую, но могу предложить попробовать либо преобразовать const char* в char*, так (char*)var или так static_cast<char*>(var), второй вариант правильнее.

Smorodov, спасибо, но это тоже не помогло. А нет какой-нибудь Си-шной ф-ии которая может считывать пути к файлам и загружать их со строковых данных?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

см.:

fopen() fclose() fread() fwrite() putc() getc()

файлы из директории раньше получали как то так (смю еще здесь: http://www.c-cpp.ru/content/findfirst-findnext ):

#include <stdio.h>
#include <dir.h>

int main(void)
{
struct ffblk ffblk;
int done;

printf("Directory listing of *.*\n");
done = findfirst("*.*", &ffblk, 0);

while(!done)
{
printf(" %s\n", ffblk.ff_name);
done = findnext(&ffblk);
}

return 0;
}[/code]

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо, уже разобрался - формирую список файлов и вручную их вбиваю на загрузку.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте учётную запись или войдите для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать учётную запись

Зарегистрируйтесь для создания учётной записи. Это просто!

Зарегистрировать учётную запись

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу

×