GIF相似度计算——C++实现

  跟着上一篇的问题,先前计算图片phash是完全python脚本进行的,其中,对于GIF的处理是基于PIL的,利用PIL提取GIF图片的第一帧,本地保存为BMP图片,而后再进行处理。
  可转C++处理后,GIF图片怎么处理呢?
  刚开始在git上搜到了一个项目:https://github.com/fridex/gif2bmp。说是能够做到GIF取其中帧,再转bmp的。试了几个,也真的可以,然后问题就来了。怎么从内存中读取bmp文件到opencv所用的mat中。
  琢磨了好一会儿,找到了个解决方法如下:

char* getphashFormMem(unsigned char *rebuf, size_t width, size_t height, size_t img_size){
//cout << width << " " << height << " " << img_size << endl;
if (img_size == 0)
return NULL;
IplImage* IplImg = GetIplFromBmp(rebuf);
Mat img(IplImg, false);
flip(img, img,0); // 翻转
……

  GIF2BMP的作用大致是,提取GIF第一帧的像素信息,保存为BMP文件格式。而后将这片内存传给getphashFormMemgetphashFormMem函数中主要利用GetIplFromBmp将BMP内存块转为IplImage指针,以方便转为最终转为所需要的Mat类型。GetIplFromBmp的实现如下:

IplImage* GetIplFromBmp(unsigned char *pBmp)  
{
unsigned char *p = pBmp;
BITMAPFILEHEADER fheader;
memcpy(&fheader, p, sizeof(BITMAPFILEHEADER));
BITMAPINFOHEADER bmphdr;
p += sizeof(BITMAPFILEHEADER);
memcpy(&bmphdr, p, sizeof(BITMAPINFOHEADER));

int w = bmphdr.biWidth;
int h = bmphdr.biHeight;
p = pBmp + fheader.bfOffBits;
IplImage *pIpl = cvCreateImage(cvSize(w, h), 8, 3);
pIpl->origin = IPL_ORIGIN_BL;
memcpy(pIpl->imageData, p, w * h * 3 * sizeof(char));
return pIpl;
}

  其实主要,就做了个复制的事情,或者说,出去头部信息后,将原始像素数据复制到IplImageimageData中。
  所以,到这里,一个稳稳当当的计算GIF图片第一帧phash的功能就完成了。(虽然,摸索的过程中,踩了很多很多坑,但的确也是忘了具体的坑了。。。记得有个坑就是错误的bmp加载到Iplimage,再转mat,mat的值会随机
  好,于是,把代码稍微优化了下,该释放内存的地方释放。然后就交给后台了,后台改了改,也就上线了。然后问题来了,这货平均每十分钟coredump一次,所以,这肯定不能放在后台当服务用了,需要重新寻找新的方法。
  再google了下,发现还有个蛮成熟的库:gif2png——http://freecode.com/projects/gif2png,看了看问题又来了,这么做的确是可以得到第一帧的png图。但又遇到了文章开头的问题,怎么从内从中直接加载png到opencv所用的mat中。
  找了一两天,没想到好办法,最后凯爷来了个链接:http://blog.csdn.net/flyingworm_eley/article/details/6523964,用freeimage去解析gif,直接提取第一帧转为IplImage格式,opencv可以直接处理IplImage。于是就能完成整个流程了。
  其实在刚开始就找到过freeimage这个库,但当时因为什么原因没编译通过,就放弃了使用这个库。不过,最后,还是用它了。所以GIF转IplImage代码如下:

IplImage* Gif2IplImage(char* fileName)
{
FreeImage_Initialise(); // 初始化freeimage,一定要FreeImage_DeInitialise()

struct stat file_stat;
int length = 0;

length = stat(fileName, &file_stat);
BYTE * bytes = (BYTE*)malloc(file_stat.st_size * sizeof(BYTE));
FILE * fin = fopen(fileName, "rb" );
if (fin)
{
fread( bytes, sizeof(BYTE),file_stat.st_size, fin);
fclose(fin);
FIMEMORY * memory = FreeImage_OpenMemory((BYTE*)bytes, file_stat.st_size);
if(NULL == memory)
{
FreeImage_DeInitialise();
return NULL;
}

FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(memory);
if(FIF_UNKNOWN == fif)
{
fprintf(stderr, "Error in Gif2IplImage: is unknow image type/n");
if(NULL != memory) FreeImage_CloseMemory(memory);
FreeImage_DeInitialise();
return NULL;
}
else if(FIF_GIF != fif) // 不是gif文件不处理
{
if(NULL != memory) FreeImage_CloseMemory(memory);
FreeImage_DeInitialise();
return NULL;
}
FIMULTIBITMAP* fiBmps = FreeImage_LoadMultiBitmapFromMemory(fif, memory, GIF_DEFAULT);// gif文件解码
if(NULL == fiBmps)
{
if(NULL != memory) FreeImage_CloseMemory(memory);
FreeImage_DeInitialise();
return NULL;
}

FIBITMAP* fiBmp = FreeImage_LockPage(fiBmps, 0);
if(fiBmp)
{
IplImage* pImg = FIBITMAP_2_IplImage(fiBmp, fif);// 将解码的图像数据封装成opencv下的IplImage格式
FreeImage_UnlockPage(fiBmps, fiBmp, false);
if(NULL == pImg)
{
if(NULL != memory) FreeImage_CloseMemory(memory);
if(NULL != fiBmps) FreeImage_CloseMultiBitmap(fiBmps, GIF_DEFAULT);
FreeImage_DeInitialise();
return NULL;
}
if(NULL != memory) FreeImage_CloseMemory(memory);
if(NULL != fiBmps) FreeImage_CloseMultiBitmap(fiBmps, GIF_DEFAULT);
FreeImage_DeInitialise();
return pImg;
}
return NULL;
}
}

  其中,调用的FIBITMAP_2_IplImage代码如下:

IplImage* FIBITMAP_2_IplImage( FIBITMAP* fiBmp, const FREE_IMAGE_FORMAT& fif)
// 将解码的图像数据封装成opencv下的IplImage格式
{
if(NULL == fiBmp || FIF_GIF != fif) return NULL;
int width = FreeImage_GetWidth(fiBmp);
int height = FreeImage_GetHeight(fiBmp);
if(width <= 0 || height <= 0)
return NULL;
RGBQUAD* ptrPalette = FreeImage_GetPalette(fiBmp);
BYTE intens;
BYTE* pIntensity = &intens;
IplImage* pImg = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
pImg->origin = 1;
for (int i = 0; i < height; i++)
{
char* ptrDataLine = pImg->imageData + i * pImg->widthStep;
for(int j = 0; j < width; j ++)
{
FreeImage_GetPixelIndex(fiBmp, j , i, pIntensity);
ptrDataLine[3*j] = ptrPalette[intens].rgbBlue;
ptrDataLine[3*j+1] = ptrPalette[intens].rgbGreen;
ptrDataLine[3*j+2] = ptrPalette[intens].rgbRed;
}
}
return pImg;
}

  最后计算phash函数:

char* getphashFormMem(IplImage* IplImg){
int size = 32; // 图片缩放后大小
int sim_re = 10; // 感知哈希长度 sim_re^2
double **iMatrix = new double*[size];
for(int i = 0; i < size; i++)
iMatrix[i] = new double[size];

char *phash = (char*)malloc(sim_re*sim_re);
memset(phash, '\0', sim_re*sim_re + 1);

Mat img(IplImg, true);
//cout << img << endl;
//flip(img, img, 0); // 反转

if(!img.data){
cout << "the image is not exist" << endl;
return NULL;
}

cvtColor(img, img, COLOR_BGR2GRAY); // 灰度化
resize(img, img, Size(size,size), 0,0, CV_INTER_CUBIC); // 缩放到32*32

DCT(img, size, iMatrix); // 离散余弦变换
DCT(iMatrix, size, iMatrix);

double averagePix = calcAverage(iMatrix, sim_re);

fingerPrint(iMatrix, sim_re, phash, averagePix);
phash[sim_re*sim_re +1] = '\0';

delete[] iMatrix;
cvReleaseImage(&IplImg);
img.release();

return phash;
}

  于是,又能愉快地计算GIF图片的phash值了,放上后台跑起来,还是挺稳定。
  项目地址:https://github.com/purpleroc/Gif_pHash

  就到这吧,发现,真的得好好找个时间来给自己做个总结、规划了!加油吧,骚年。

——Tracy_梓朋
2017年5月3日18:34:45