《Efficient Graph-Based Image Segmentation》c++代码解读

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/McEason/article/details/99544497

本文并没有将全部的代码放上来,只放了些觉得关键的部分。
论文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];
  }
}  

如果有什么写错了的地方,恳请大家指正,感谢!

猜你喜欢

转载自blog.csdn.net/McEason/article/details/99544497
今日推荐