本文的基本结构:
论文的笔记
源码的分析笔记
一 本文的分割思想——无向图分割
1 名词和符号介绍
无向图,每个像素是一个顶点,
是顶点集合,是邻边集合,
(component):可以理解为分割后的每一个部分(类似region)
(segmentation)是划分成components的结果,每个component
2 分割的思想
2.1 分割原则
在同一个component内相邻顶点的边的权值应尽量小,不同component内的顶点的权重较大
2.2 分割方法
定义一个参数:其值判断了两个component的之间是否有边界(boundary)即要不要合并
定义internal difference (内差):代表一个部分(component)的属性
对于一个component 是C的最小生成树中的最大权值,即
定义外差:代表了两个部分间的属性
两个component的差值为
若两个component之间无任何邻边,则值等于无穷
2.3 分割的判断条件
至此,可以定义
的法则为,大于至少其中一个component的内差(即),
表达式为:
其中,
是阈值函数,控制外差需要大于内差的最小值
阈值函数的计算公式为
是的大小,也就是顶点个数,是一个常量参数,这样做也保护了小的component(数量较少)在进行判断时,不容易被判定为true
3 分割算法
3.1 分割情况的定义
称分割是too fine:个人认为意思就是分的太细了
称分割是too coarse:too fine的对立(分的太粗了)
proper refinement:假设对于一个图像,有两个分割结果T,S,都是存储了分割后的components;
若T的每一个component都是S中某个component的子集(或两个component相等,则称T是S的一个proper refinement,T比Sfiner,S比Tcoarse(T分的太细了,S分的太粗了
3.2 算法伪代码
简单来说就是
【初始化】将每个像素看成无向图的一个顶点,相邻像素连接形成边,边的权重是计算的两个顶点的不相似度 ,按权重从小到大对边排序
【分割】初始状态每个顶点都是一个component,然后逐边判断两个顶点用上文中的D函数判断是否要分割开,逐渐的融合component;重复操作直至到我们的循环退出条件
【退出条件】当前的分割结果为既不是too fine也不是too coarse(初始条件为too fine,因为每个顶点都是component,肯定是太细了,逐渐向coarse靠拢,文章中证明了这个算法是可以实现要求的分割结果的,是一个单调的过程)
二 看代码
文件包里有文件
- image.h
- disjoint-set.h
- segment-graph.h
- imutil.h
- convolve.h
- imconv.h
- segment-image.h
我排了一下顺序,大致按了层级关系,即序号越大的文件越需要包含前面头文件里定义的函数
1 首先第一个文件image.h
1.1 定义了一个使用类模板的image类,因为分割算法就是在图像上进行的,所以定义图像类方便后续的操作
类中的方法有
- image(const int width,const int height,const bool init):初始化宽,高以及init;init为true时,图像用0填充
- ~image():删除
- void init(const T &val):用val填充整个图像
- image<T> *copy() :复制一个新的图像
- int width():取图像的宽度
- int height():取图像的高度
1.2 define了两个函数:
imRef(im,x,y):取图像im在坐标(x,y)上的值
imPtr(im,x,y): 取图像im在坐标(x,y)处的指针
2 第二个文件disjoint-set.h(中文译名不相交集)
2.1 头文件
包含头文件image.h
2.2 内容
2.2.1定义结构体uni_elt(表示森林):
结构体内有三个变量:
- rank
- p: 树的根节点,
- size: 树包含的结点数
2.2.2 定义类universe
universe的方法有
- universe(int elements)初始化一个长度为elements的uni_elt数组,各元素的rank为0,size = 1,p = i
- ~universe() 删除
- int find(int x) 找x所在树的祖先节点
- void join(int x, int y):合并结点x,y所在的树
3 第三个文件 segment-graph.h
3.1 包含头文件"disjoint-set.h"
3.2 内容
3.2.1 结构体edge:{ 权值,顶点编号}
3.2.2 阈值函数THRESHOLD
3.2.3 重载:重载运算符 < 比较结构体a,b的权值
3.2.4 定义函数segment_graph(int num_vertices,int num_edges, edge *edges, float c) 返回universe类的对象
函数实现的操作:
- 将edges按权值从小到大排序
- 生成num_vertices数量的不相交森林
- 初始化一个阈值数组,对应森林的每棵树的阈值
- 循环遍历每个边
- 判断当前边是否属于一棵树
- 若不在一棵树上,判断边的权值是否小于两端节点的阈值
- 若均小于,则融合两端节点所在的两颗树,更新根结点,更新根节点的阈值
4 imutil.h
包含了两个函数
min_max :返回输入图像的最大值和最小值
threshold :返回一个新的图像,其值为原图像对应位置与阈值t比较后的结果
5 convolve.h
5.1 介绍
这个文件里定义了两种图像卷积的方法,实现卷积操作
5.2 定义函数
函数convolve_even 卷积(对像素):相当于以pixel为起点,沿x的方向放置滤波器,朝左和朝右都要放置(对称),然后对应位置先相乘,再两个方向相乘的值相加,起点位置只乘一次,所有的乘积和相加作为新的图像相应位置的像素值
函数convolve_odd 卷积:同理,只不过两个方向的相乘和是相减的(朝左方向的乘积和减去朝右方向的)
6 imconv.h
6.1 介绍
定义了一堆函数,其功能都是对图像的数据类型的转换,太多了就不列述了
6.2 部分函数
函数imageRGBtoGRAY:将RGB图像转换成灰度图像,RGB权值分别为0.299 0.587 0.114
函数imageGRAYtoRGB:灰度图像转RGB
函数imageUCHARtoFLOAT:图像中像素值的数据类型的转换
7 filter.h
7.1 介绍
这个头文件就正式对图像进行卷积操作了,定义的都是实现滤波操作的方法
7.2 内容
A 函数normalize:归一化
B 宏定义建立一个filter函数:可以指定函数名以及滤波器使用的函数fun,
代码里定义了一个高斯滤波器make_gaussian,用的是
【滤波器输入参数】
文件中默认
【函数的作用】返回一个vector类型的mask,长度为len,每个位置对应的值为由输入的计算
C smooth函数
对输入函数卷积两次,得到一个滤波后的相同尺寸的图像
D laplacian函数
解释太累了 ,就是逐像素做上述计算操作
来到了最重点的一个头文件
8 segment-image.h
8.1 介绍
这个头文件就是综合了上述各文件中的函数方法,开始实现对图像的分割操作
8.2 内容
A 函数diff:像素的不相似度的计算
计算两个点的r,g,b三通道分别的差值的平方和的均方根
B segment_image(rgb图像
实现的操作:
- 对每个通道用参数为sigma的滤波器滤波
- 建立一个无向图edges,遍历全像素,链接相邻的像素(方向为向右,向下,右下,右上),存储每个边,权值由diff函数计算
- 用segment_graph分割无向图
- 处理一些小的components,合并小于min_size的树
- 给处理后的图像各个component随机选一个颜色上色
- 就可以输出啦!
三 补充说明
1 开源代码里还有两个文件misc.h和pnmfile.h 前者是重载了很多运算符,后者是文件操作,用来读写图片的,就不细细学习了。
2 运行代码包里的segment.cpp 就能实现分割