将一张图像划分成许多个小块,每个小块对比素材库中的图像,选择一张色彩分布最相近的代替该小块。
也就是利用素材库里的图像拼合成原图像的蒙太奇图像。蒙太奇图像大体看上去与原图像相似,细看就像是打了马赛克。
图像相似度计算之哈希值方法一文提到用感知哈希算法计算相似度:
感知哈希算法(perceptual hash algorithm),它的作用是对每张图像生成一个“指纹”(fingerprint)字符串,然后比较不同图像的指纹。结果越接近,就说明图像越相似。
实现步骤:
缩小尺寸:将图像缩小到8*8的尺寸,总共64个像素。这一步的作用是去除图像的细节,只保留结构/明暗等基本信息,摒弃不同尺寸/比例带来的图像差异;
简化色彩:将缩小后的图像,转为64级灰度,即所有像素点总共只有64种颜色;
计算平均值:计算所有64个像素的灰度平均值;
比较像素的灰度:将每个像素的灰度,与平均值进行比较,大于或等于平均值记为1,小于平均值记为0;
计算哈希值:将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图像的指纹。组合的次序并不重要,只要保证所有图像都采用同样次序就行了;
得到指纹以后,就可以对比不同的图像,看看64位中有多少位是不一样的。在理论上,这等同于”汉明距离”(Hamming distance,在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数)。如果不相同的数据位数不超过5,就说明两张图像很相似;如果大于10,就说明这是两张不同的图像。
但是只记录像素的灰度与平均灰度值谁大谁小的哈希值效果很差,改为记录64个像素的像素值效果好很多,当然速度要慢;
void clcHash(Mat& block, vector & hash)
{
//block为要计算hash值的图像块或者素材
//hash保存缩小后图像每个像素的像素值,以此作为该图像的hash值
Mat small;
resize(block, small, Size(hashCol, hashRow), 0, 0, INTER_CUBIC);
cvtColor(small, small, CV_BGR2GRAY);
for (int row = 0; row < hashRow; row++)
{
int count = row * hashRow;
Vec3b* ptr = small.ptr(row);
for (int col = 0; col < hashCol; col++)
{
hash[count + col] = ptr[col];
}
}
}
计算过所有块与所有素材的哈希值之后,每个块依次与所有素材进行哈希值对比,选择哈希值最相近的素材。
int compareHash(const vector & blockHash, const vector<vector >& pictrueHashVec)
{
//blockHash为当前进行对比的块的hash值
//pictureHashVec为所有素材的hash值
int minDiff = hashNum+1; //hashNum=hashCol*hashRow
int index = 0; //素材在素材容器中的索引
for (int i = 0; i < pictrueHashVec.size(); i++)
{
int count = 0;
int diff = 0;
for (; count < hashNum; count++)
{
if (abs(blockHash[count][0] - pictrueHashVec[i][count][0])>30 ||
abs(blockHash[count][1] - pictrueHashVec[i][count][1])>30 ||
abs(blockHash[count][2] - pictrueHashVec[i][count][2])>30)
{
diff++;
}
}
if (diff < minDiff)
{
minDiff = diff;
index = i;
}
}
return index;
}
经过哈希值对比之后,每个块都计算出与其最相近的素材,其实计算出的是素材的索引,根据索引可以找到对应素材。将该块与块对应的素材按比例融合即可。
//dst:原图像副本,pictureVec:素材容器,pictureIndex:每个块对应的素材索引,
//blockNumRow:原图每列划分块的数量,blockNumCol:原图每行划分块的数量
//blockCol:块的宽度, blockRow:块的高度
//omg:融合比例
void mosaic(Mat& dst, vector & pictrueVec, int pictureIndex[][blockNumCol], int blockNumCol, int blockNumRow, int blockCol, int blockRow,float omg)
{
for (int row = 0; row < blockNumRow; row++)
{
for (int col = 0; col < blockNumCol; col++)
{
Mat imgROI = dst(Rect(col*blockCol,row*blockRow,blockCol,blockRow));
int index = pictureIndex[row][col];
addWeighted(imgROI, omg, pictrueVec[index], (1.0 - omg), 10, imgROI);
}
}
}
原图
75*150个块,融合比例0.4
50*100个块,融合比例0.4
素材库素材色彩有些单调,影响了最终结果。程序没有任何优化,所以速度有些慢。