对于我们的风格迁移社区网站,为了加强用户的社区感,我们希望做一个新的功能:用户行为匹配。也就是用户可以根据他想上传的一张图片,从其他用户的图片库中检索出一张与这个图片在颜色和纹理上比较相似的图片,并得到该相似图片的用户信息、用户风格迁移历史等。
这个系统中比较核心的部分是图像检索模块:如何匹配图片的相似度。为此,我研究了数种基于内容的图像检索方法:
首先通过查询相关资料,了解了感知哈希算法的相关知识。感知哈希算法将图片放缩为8*8的大小,通过每一块和平均像素值的比较,确定该区域是更亮(1)或更暗(0),从而对每张图片产生一个64位的指纹。核心代码如下:
string Color_PHash(int srcPicNum, int mode) { Mat src; if (mode == TRAINING_MODE) { string srcPath = getPath(srcPicNum); src = imread(srcPath, CV_LOAD_IMAGE_COLOR); } else { src = UserChosenMat; } Mat img, dst; string rst(64, '\0'); double dIdex[64]; double mean = 0.0; int k = 0; if (src.channels() == 3) { cvtColor(src, src, CV_BGR2GRAY); img = Mat_<double>(src); } else { img = Mat_<double>(src); } //1.Resize to 8*8 resize(img, img, Size(8, 8)); //2.dct dct(img, dst); //3.求左上角dct系数 8*8 for (int i = 0; i < 8; ++i) { for (int j = 0; j < 8; ++j) { dIdex[k] = dst.at<double>(i, j); mean += dst.at<double>(i, j) / 64; ++k; } } //4.calculate Hash for (int i = 0; i<64; ++i) { if (dIdex[i] >= mean) { rst[i] = '1'; } else { rst[i] = '0'; } } return rst; }
接下来,需要用汉明距离判断两张图片的指纹的距离,从而得到图片之间的相似度结果。
int Retrieval_Color_PHash() { ifstream in; in.open("color_phash.txt", ios::in); if (in.fail()) return -1; string phash = Color_PHash(-1, TEST_MODE); string line; int len = phash.size(); int minRes = 1e3; int sign = -1; int res = 0; for (int i = 1; i <= MAX_PICNNUM; i++) { getline(in, line, '\n'); // cout << line << endl; res = 0; for (int j = 0; j<len; j++) { if (phash[j] != line[j]) { res++; } } if (res<minRes) { // cout << minRes << endl; minRes = res; sign = i; } } cout << "pHash算法选出的图片是:" << sign << endl; cout << "差异是" << minRes << endl; return sign; }
感知哈希算法的训练时间和检索时间都比较快,在2s之内就可以检索完毕,效果还不错。
我研究的第二个方法是基于hsv彩色直方图,通过比较彩色直方图的相似程度来进行相似度判断。
hsv彩色直方图的构造:(需要对灰度级进行降采样,否则图片的 指纹过长,增加检索消耗):
string Color_hsv_hist(int srcPicNum, int mode) { Mat src, hsv; int bin[64]; //8*h + s for (int i = 0; i < 64; i++) { bin[i] = 0; } if (mode == TRAINING_MODE) { src = imread(getPath(srcPicNum), CV_LOAD_IMAGE_COLOR); } else { src = UserChosenMat; } resize(src, src, Size(45, 45)); cvtColor(src, hsv, COLOR_RGB2HSV); vector<cv::Mat> hsv_vec; split(hsv, hsv_vec); Mat H = hsv_vec[0]; Mat S = hsv_vec[1]; H.convertTo(H, CV_32F); S.convertTo(S, CV_32F); for (int i = 0; i < src.rows; i++) { for (int j = 0; j < src.cols; j++) { float h = H.at<float>(i, j); float s = S.at<float>(i, j); //cout << "h:" << h << ",s:" << s << endl; int hi = ((int)h-1) / 45 * 2; int yy = ((int)h - 1) % 45; hi = (yy >= 22) ? (hi + 1) : hi; int si = ((int)s - 1) / 32; int bidx = hi * 8 + si; bin[bidx]++; } } string hsv_code; for (int i = 0; i < 64; i++) { hsv_code.append(to_string(bin[i])); if (i != 63) hsv_code.push_back(','); } //cout << hsv_code << endl; //string windowName = "tests"; //namedWindow(windowName, CV_WINDOW_AUTOSIZE); //imshow(windowName, hsv); return hsv_code; }
对于彩色直方图的相似度衡量,有两种方法,第一种是逐个bin比较两者的交集,也就是取相应bin中小的那个值进行相加;另一种是计算巴氏距离:相应的bin的数值相乘,开方,求和。
交集相似度:
int Retrieval_Color_hsvhist_intersect() { ifstream in; in.open("color_hsvhist.txt", ios::in); if (in.fail()) return -1; string hsvcode = Color_hsv_hist(-1, TEST_MODE); int dstArr[64] , curArr[64]; memset(dstArr, 0, 64 * sizeof(int)); memset(curArr, 0, 64 * sizeof(int)); vector<string> strvec = split(hsvcode, ","); for (int i = 0; i < 64; i++) { dstArr[i] = stoi(strvec[i]); //cout << dstArr[i] << endl; } string line; int maxRes = 0; int winner = -1; int res = 0; for (int i = 1; i <= MAX_PICNNUM; i++) { getline(in, line, '\n'); res = 0; memset(curArr, 0, 64 * sizeof(int)); strvec.clear(); strvec = split(line, ","); for (int i = 0; i < 64; i++) { curArr[i] = stoi(strvec[i]); res += min(curArr[i], dstArr[i]); } if (res>maxRes) { cout << maxRes << endl; maxRes = res; winner = i; } } cout << "直方图交集算法选出的图片是:" << winner << endl; cout << "直方图上相交的像素个数是" << maxRes << endl; return winner; }
巴氏距离相似度:
int Retrieval_Color_hsvhist_Bhattacharyya() { ifstream in; in.open("color_hsvhist.txt", ios::in); if (in.fail()) return -1; string hsvcode = Color_hsv_hist(-1, TEST_MODE); int dstArr[64], curArr[64]; memset(dstArr, 0, 64 * sizeof(int)); memset(curArr, 0, 64 * sizeof(int)); vector<string> strvec = split(hsvcode, ","); for (int i = 0; i < 64; i++) { dstArr[i] = stoi(strvec[i]); } string line; int maxRes = 0; int winner = -1; int res = 0; for (int i = 1; i <= MAX_PICNNUM; i++) { getline(in, line, '\n'); res = 0; memset(curArr, 0, 64 * sizeof(int)); strvec.clear(); strvec = split(line, ","); for (int i = 0; i < 64; i++) { curArr[i] = stoi(strvec[i]); res += (int)(sqrt(curArr[i]* dstArr[i])); } if (res>maxRes) { cout << maxRes << endl; maxRes = res; winner = i; } } cout << "直方图巴氏距离算法选出的图片是:" << winner << endl; cout << "直方图上巴氏距离的数值是" << maxRes << endl; return winner; }
经过试验发现,两种算法的时间都在10秒左右,巴氏距离得到的检索结果更好一些。