版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
文章目录
本文并没有将全部的代码放上来,只放了些觉得关键的部分。
论文PDF和c++代码下载处
1.需要输入的参数:sigma、阈值k、min(component的最小size)、输入、输出(存放的变量)。
2.生成G=(V,E)
读取图片的信息,RGB三个通道信息分别读取,然后对三个通道进行高斯平滑,去除伪阴影(这块代码还没搞懂,感觉怪怪的,显示一个没见过的正则化,然后高斯过滤的操作也很奇特)
//用了重载函数
static image<float> *smooth(image<float> *src, float sigma) {
std::vector<float> mask = make_fgauss(sigma);
normalize(mask);
//mask是滤波器
image<float> *tmp = new image<float>(src->height(), src->width(), false);
image<float> *dst = new image<float>(src->width(), src->height(), false);
//卷积两次,卷积后的结果放在第二个参数中
convolve_even(src, tmp, mask);
convolve_even(tmp, dst, mask);
delete tmp;
return dst;
}
//mask的len,不在这个位置,但我觉得挺重要,就先放在这里看着
//int len = (int)ceil(sigma * WIDTH) + 1;
static void convolve_even(image<float> *src, image<float> *dst,
std::vector<float> &mask) {
int width = src->width();
int height = src->height();
int len = mask.size();
//这个卷积卷的我头皮发麻,为什么一直在一行上操作???
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float sum = mask[0] * imRef(src, x, y);
for (int i = 1; i < len; i++) {
sum += mask[i] *
(imRef(src, std::max(x-i,0), y) +
imRef(src, std::min(x+i, width-1), y));
}
imRef(dst, y, x) = sum;
}
}
}
生成edge的结构体,edge的结构体如下
typedef struct {
float w;
int a, b;
} edge;
计算edge权重的方法,就是对RGB三色通道求欧式距离
static inline float diff(image<float> *r, image<float> *g, image<float> *b,
int x1, int y1, int x2, int y2) {
return sqrt(square(imRef(r, x1, y1)-imRef(r, x2, y2)) +
square(imRef(g, x1, y1)-imRef(g, x2, y2)) +
square(imRef(b, x1, y1)-imRef(b, x2, y2)));
下面是edge的生成
//一个点和周围八个点相邻,算上重复的边最多不到8*n,但是两个点共用一个边,所以最多不到4*n
edge *edges = new edge[width*height*4];
int num = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//这里计算了左右两个点之间的边
if (x < width-1)
{
edges[num].a = y * width + x;
edges[num].b = y * width + (x+1);
edges[num].w = diff(smooth_r, smooth_g, smooth_b, x, y, x+1, y);num++; }
//这里计算上下两个点之间的边
if (y < height-1)
{
edges[num].a = y * width + x;
edges[num].b = (y+1) * width + x;
edges[num].w = diff(smooth_r, smooth_g, smooth_b, x, y, x, y+1);num++; }
//这里计算该点和右下两个点之间的边
if ((x < width-1) && (y < height-1))
{
edges[num].a = y * width + x;
edges[num].b = (y+1) * width + (x+1);
edges[num].w = diff(smooth_r, smooth_g, smooth_b, x, y, x+1, y+1);num++; }
//这里计算和左下两个点之间的边
if ((x < width-1) && (y > 0))
{
edges[num].a = y * width + x;
edges[num].b = (y-1) * width + (x+1);
edges[num].w = diff(smooth_r, smooth_g, smooth_b, x, y, x+1, y-1);num++; }
}
}
3.合并component
此时,edge已经构造完成,而且已知边的数量,我们之后对图片进行分割
#define THRESHOLD(size, c) (c/size)
universe *segment_graph(int num_vertices, int num_edges, edge *edges,
float c) {
//先把权重排序,升序
std::sort(edges, edges + num_edges);
//建立一个分裂的森林,每个顶点就是一棵树,树的数量就是顶点的数量
universe *u = new universe(num_vertices);
//初始化阈值,阈值的个数和顶点数相同(为了动态设置阈值?)
float *threshold = new float[num_vertices];
for (int i = 0; i < num_vertices; i++)
//把每个阈值设置成 c/1 也就是k/1
threshold[i] = THRESHOLD(1,c);
//边是升序,合并的顺序由边决定,把每个边都处理了一遍,反正一个边就能连两个顶点,
//这样一次循环所有component工作都做好了
for (int i = 0; i < num_edges; i++) {
edge *pedge = &edges[i];
// 将不同component连接
int a = u->find(pedge->a);
int b = u->find(pedge->b);
//先试探a和b是否已经合并
//如果找了个edge比两个component的阈值小,那就合并
if (a != b) {
if ((pedge->w <= threshold[a]) &&
(pedge->w <= threshold[b])) {
u->join(a, b);
//这里a或b无所谓
a = u->find(a);
//合并之后改变阈值,阈值=连接两个点的边的权重 + size变化后的阈值
//对应论文中的:每次合并的时候花固定的时间保存component的Int,导致合并的边就是一个component的MST的最大的权重边
threshold[a] = pedge->w + THRESHOLD(u->size(a), c);
}
}
}
// free up
delete threshold;
return u;
}
上面的代码用到了一个类 universe ,用来处理顶点之间的寻找、合并等问题。
//这个结构体用来存放每个component,
//rank是排名,用来决定该合并到那个component中
//p是该component属于那个大的component的序号,
//size是该component的大小
typedef struct {
int rank;
int p;
int size;
} uni_elt;
class universe {
public:
universe(int elements);
~universe();
int find(int x);
void join(int x, int y);
int size(int x) const { return elts[x].size; }
int num_sets() const { return num; }
private:
uni_elt *elts;
int num;
};
//初始化
universe::universe(int elements) {
//生成与顶点个数相同的uni_elt结构体,elts
elts = new uni_elt[elements];
//num初始化为顶点数量,其实代表compontent数量,每次合并都会减少num数量
num = elements;
//每个elts的排序都是0,size都是1,p对应每个顶点合并到了哪个component中
//这样,通过将 顶点序号 送入elts[]就能获知合并到了哪个component中
for (int i = 0; i < elements; i++) {
elts[i].rank = 0;
elts[i].size = 1;
elts[i].p = i;
}
}
universe::~universe() {
delete [] elts;
}
//输入的是顶点的序号。如果顶点序号和自己的elts中对应的不一样
// 那么说明该elts是合并过的component
//那么就返回合并到最后的component序号
//否则原样返回
int universe::find(int x) {
int y = x;
while (y != elts[y].p)
y = elts[y].p;
elts[x].p = y;
return y;
}
//将两个component合并,合并规则如下:
//rank小的合并到rank大的中去
//rank相等的话,任意挑选一个合并进去,被合并的rank++
//合并完之后num--
void universe::join(int x, int y) {
if (elts[x].rank > elts[y].rank) {
elts[y].p = x;
elts[x].size += elts[y].size;
} else {
elts[x].p = y;
elts[y].size += elts[x].size;
if (elts[x].rank == elts[y].rank)
elts[y].rank++;
}
num--;
}
现在,我们完成了MST的生成,由于有些component的size会小于我们要求的min size,所以接下来我们将size很小的component就近合并。
//这个num是edeg的数量
for (int i = 0; i < num; i++) {
int a = u->find(edges[i].a);
int b = u->find(edges[i].b);
if ((a != b) && ((u->size(a) < min_size) || (u->size(b) < min_size)))
u->join(a, b);
}
4.对component进行颜色填充
至此,大部分的工作已经完成了,接下来就是收尾可视化,给最后的component随机生成color.
先是随机颜色函数
rgb random_rgb(){
rgb c;
double r;
c.r = (uchar)random();
c.g = (uchar)random();
c.b = (uchar)random();
return c;
}
然后生成一个新的图像,这个图像就是效果展示图。
//新生成一个图像,用来存放最后的每个component对应的color的图片
image<rgb> *output = new image<rgb>(width, height);
//给最后留下的component随机挑选color
rgb *colors = new rgb[width*height];
for (int i = 0; i < width*height; i++)
colors[i] = random_rgb();
//给新图片每个像素赋color,同属于一个component的像素的颜色相同
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int comp = u->find(y * width + x);
imRef(output, x, y) = colors[comp];
}
}
如果有什么写错了的地方,恳请大家指正,感谢!