[OpenCV+VS2015]meanshift算法
目录
1 meanshift介绍
这里有很多大神介绍,小弟我也不再造轮子了。参考文献
1.1 meanshift自己的一点理解
我个人认为是(纯属个人观点,如有不同,欢迎交流)
如果从数学角度看,是让一个圆形的中心向着圆形的重心移动,即圆心一直往数据集密度最大的方向移动;
如果从物理的角度看,是力的合成与物体的运动。每次迭代通过求取力的合成向量,然后让圆心沿着力的合成方向,移动到新的平衡位置;
如果从图像的角度来看,是让从人眼看起来相似的像素聚类合并
1.2 meanshift个人简化理解
感觉论文中的公式比较高深,我根据理解总结了一个别的公式:
理解到这里就可以按着下面思想撸程序了
2 meanshift算法
2.1 OpencvAPI
opencv有自带的API处理,函数为pyrMeanShiftFiltering()
2.2 编写meanshift
opencv中的meanshift不是圆形窗口而是方形窗口,然后用的好像不是高斯函数,嗯记不得了。
然后这里不仅要计算窗口每个点到中心的距离,还要考虑每个点和中心点的颜色距离!!
比方说:A、B喊我吃饭,
A说吃火锅,B说吃烧烤,那我看A先喊我B后喊我,那我就决定吃火锅了
但是如果考虑B跟我交情好
那我还得考虑交情的情况下,我决定吃烧烤去了
但是吧,A又先喊的我,我也总得考虑吧,hhh那我就和A、B去吃大排档(烧烤火锅都有)这就是meanshift中往均值方向走
2.2.1 LUV色彩空间
什么要转换到LUV空间之后再做meanshift呢,因为人眼看到的颜色近似度和RGB近似度是不一样的
举个例子(0,0,0)白色、(10,0,0)红色、(10,10,10)灰色 RGB下
那人眼看肯定灰色和白色感觉相近吧,白色和红色肯定色差大。
!!!!!但是!!但是!!从rgb数据上看红色到白色的欧式距离短呀,
那这肯定不符合人眼规律喽。
所以————
我这里直接上图了
首先要把0~255的RGB数据归一化,然后用上面公式转到sRGB下,其中有一个gamma校正。
然后转到XYZ下。
然后在XYZ坐标下转换到LUV空间下
引图
这里有个D65/2°下的基准白95.047,100.000,108.883
void RGBtoLUV( Color & color, Color & color_LUV)
{
Color WhiteReference_XYZ = { 95.047,100.000,108.883 };
Color color_XYZ;
double r = color[0] / 255.0, g = color[1] / 255.0, b = color[2] / 255.0;
r = (r > 0.04045 ? pow((r + 0.055) / 1.055, 2.4) : r / 12.92) * 100.0;
g = (g > 0.04045 ? pow((g + 0.055) / 1.055, 2.4) : g / 12.92) * 100.0;
b = (b > 0.04045 ? pow((b + 0.055) / 1.055, 2.4) : b / 12.92) * 100.0;
// Observer. = 2°, Illuminant = D65
color_XYZ[0] = r * 0.4124 + g * 0.3576 + b * 0.1805;
color_XYZ[1] = r * 0.2126 + g * 0.7152 + b * 0.0722;
color_XYZ[2] = r * 0.0193 + g * 0.1192 + b * 0.9505;
const double Epsilon = 0.008856; // Intent is 216/24389
const double Kappa = 903.3; // Intent is 24389/27
double y = color_XYZ[1] / WhiteReference_XYZ[1];
color_LUV[0] = (y > Epsilon ? 116.0 * pow(y, 1.0 / 3.0) - 16.0 : Kappa * y);
double targetDenominator = color_XYZ[0] + color_XYZ[1] * 15.0 + color_XYZ[2] * 3.0;
double referenceDenominator = WhiteReference_XYZ[0] + WhiteReference_XYZ[1] * 15.0 + WhiteReference_XYZ[2] * 3.0;
// ReSharper disable CompareOfFloatsByEqualityOperator
double xTarget = targetDenominator == 0 ? 0 : ((4.0 * color_XYZ[0] / targetDenominator) - (4.0 * WhiteReference_XYZ[0] / referenceDenominator));
double yTarget = targetDenominator == 0 ? 0 : ((9.0 * color_XYZ[1] / targetDenominator) - (9.0 * WhiteReference_XYZ[1] / referenceDenominator));
// ReSharper restore CompareOfFloatsByEqualityOperator
color_LUV[1] = (13.0 * color_LUV[0] * xTarget);
color_LUV[2] = (13.0 * color_LUV[0] * yTarget);
}
2.2.2 meanshift平滑
1、这里用的是容器vector和自己定义的class去导入图片数据,
2、同时meanshift求值的时候要用双线性插值法去求移动后的点的像素值
void meanshift_filter(const Image & src, Image & dst, const double sr = 6, const double tr = 8) {
const unsigned long max_itr = 10; //最大的迭代次数
const double color_threshold = 0.05;
assert(!src.empty());
for (unsigned long i = 0; i < src.size(); i++)
{
assert(src[i].size() == src[0].size());
}
assert(sr > 0 && tr > 0);
dst.clear(); //容器清空操作
for (unsigned long i = 0; i < src.size(); i++)
{
Row dst_row;
for (unsigned long j = 0; j < src[i].size(); j++)
{
double cur_row = i;
double cur_col = j;
Color cur_color = src[i][j];
for (unsigned long k = 0; k < max_itr; k++)
{
double ltr = (cur_row - sr - 1);// left top row.
double ltc = (cur_col - sr - 1);// left top col.
double rbr = (cur_row + sr + 1);// right bottom row.
double rbc = (cur_col + sr + 1);// right bottom col.
ltr = ltr < 0 ? 0 : ltr;
ltc = ltc < 0 ? 0 : ltc;
rbr = rbr > src.size() ? src.size() : rbr;
rbc = rbc > src[i].size() ? src[i].size() : rbc; //确保在图片内
double displacement_r = 0;
double displacement_c = 0;
double denominator = 0;
for (unsigned long l = ltr; l < rbr; l++)
{
for (unsigned long m = ltc; m < rbc; m++) //遍历窗口中的点
{
double temp = cur_row - l;
double distant_sq = 0;
distant_sq += (temp*temp);
temp = cur_col - m;
distant_sq += (temp*temp); //求取位置距离
if (distant_sq <= sr*sr) //在窗口内(为圆形),那么就要求取像素色彩距
{
double color_distance = 0;
for (int c = 0; c < channels; c++)
{
temp = src[l][m][c] - cur_color[c]; //窗口中的点减去中心点
color_distance += (temp*temp); //三个通道的色彩距 (0,0,0)(50,0,0)(50,50,50)RGB色彩空间
}
color_distance = sqrt(color_distance); //求取像素色彩距离
double weight = Guass(color_distance / tr); //根据像素色彩距,做出权重
displacement_r += (l - cur_row) * weight; //进行替换
displacement_c += (m - cur_col) * weight;
denominator += weight; //用于求和归一
}
}
}
double dr = displacement_r / denominator;
double dc = displacement_c / denominator;
cur_row += dr;
cur_col += dc; //向量进行平移操作
Color pre_color = cur_color;
if (cur_row < 0 || cur_row >= src.size() - 1 || cur_col < 0 || cur_col >= src[i].size() - 1)
{
int cur_r = cur_row;
int cur_c = cur_col;
cur_r = cur_r < 0 ? 0 : cur_r;
cur_c = cur_c < 0 ? 0 : cur_c;
cur_r = cur_r >= src.size() ? src.size() - 1 : cur_r;
cur_c = cur_c >= src[i].size() ? src[i].size() - 1 : cur_c;
cur_color[0] = src[cur_r][cur_c][0];
cur_color[1] = src[cur_r][cur_c][1];
cur_color[2] = src[cur_r][cur_c][2];
} //边界操作
else
{
for (int c = 0; c < channels; c++)
{
int lt_r = cur_row, lt_c = cur_col;
double temp1 = src[lt_r][lt_c][c] + (cur_col - lt_c)*(src[lt_r][lt_c + 1][c] - src[lt_r][lt_c][c]);
double temp2 = src[lt_r + 1][lt_c][c] + (cur_col - lt_c)*(src[lt_r + 1][lt_c + 1][c] - src[lt_r + 1][lt_c][c]);
cur_color[c] = temp1 + (cur_row - lt_r)*(temp2 - temp1); //插入多少的像素值
}
}
// 双线性插值
/*循环迭代次数*/
double color_distance = 0;
for (int c = 0; c < channels; c++)
{
double temp = pre_color[c] - cur_color[c];
color_distance += (temp*temp);
}
if ((abs(dr) < EQUAL_ERR && abs(dc) < EQUAL_ERR) || color_distance < color_threshold*color_threshold || k == max_itr - 1)
{
dst_row.push_back(cur_color);
break;//用于退出迭代
}
}
}
dst.push_back(dst_row);
}
}
3 效果
电脑不行,跑个程序跑五六分钟hhh
然后就是meanshift的聚类以及分割还不太懂,邻接表,传递闭包还不太明白,后面有空再学习!
##4 补充一下自己学习做的笔记(5.13补记)