伴随着研究生阶段课程的开始,也学习到了不少有趣的东西,这里就介绍下随着《计算机图形技术》课程中老师介绍的Seam Carving
(也叫Content Aware
即内容感知)算法,算法是由Shai Avidan
, Ariel Shamir
在2007年提出。这里就以个人的理解来尽可能的详细解读这篇文章
算法用途
正如标题那样,Seam Carving
算法主要是用于图片的缩放(裁剪、拉伸),但其不同于传统算法,传统算法的处理是对于整张图片的,在处理图片时,对于图像中的每个像素点,均采用了同等的对待方式,因此经过拉伸之后(尤其是非等比拉伸的情况下),会出现明显的形变。
以下图的为例
上为640x426像素的,我们希望将其压缩至300x426。
第一种处理方式便是裁剪,这里采用的是居中裁剪,可以看出,经过裁剪之后,图片中的部分信息缺失了。
第二种是缩放,缩放是尤其不等比缩放时会产生明显的形变
第三种便是content-aware
,基于内容感知算法对图片进行处理,保持了图片主要部分的相关特性。可以看到,其基本上保持了时钟塔的大小与比例,并且对底层部分进行了一定的压缩。
那么这个算法究竟是如何实现的呢?
算法原理-压缩
1. 感知内容
图像在计算机中其实只是由像素组成的矩阵,其中每个像素由R、G、B分别为0-255的数字构成。通过屏幕的渲染,便将数据转换为了人眼可以分辨出的图像
问题也随之而来,计算机要如何从像素矩阵中感知内容?
能量函数=>能量矩阵
这里Seam-Carving
巧妙的将感知内容部分抽象成了一个可以替换的接口部分并将其定义为了能量函数(Energy Function) 。此部分接口可以被自定义实现,从而影响算法后续的裁剪效果。
从接口层面的抽象来看,Seam-Carving
算法会将,为经处理过的原图矩阵数据移交给接口,接口只需返回对应的图像能量矩阵。
能量矩阵其实代表了原图像矩阵中的某个像素点的重要程度,即该像素点在图中越重要,其对应位置的能量矩阵中的能量便会越高。
这里我使用过如下几个能量计算函数:
自定义能量函数
这个函数是我自己想的,用于做测试的。其原理相当于是计算当前像素点分别与其上下左右像素点之间的差值绝对值的均值。
对于示例图,其最终计算能量矩阵的效果图下:
图中越明亮的部分,其像素重要程度越高。
对比原图可以发现,自定义算法中对边缘区域识别较为敏感,其赋予了边缘区域较高的能量
Sabel算子(图像梯度)
Sabel算子法这里不过多描述了,主要是用于这里计算图像中心点梯度走向(变化率趋势)。与自定义能量函数相比,其增加了目标像素点周围角上的像素影响。
对于示例图,其最终计算能量矩阵的效果图下:
对比原图与自定义能量函数可以看出,其算法对于边缘识别更加明显。
Forward Energy
Forward Energy算法是于2008年提出的,这里我并没有详细的去找到对应的算法具体实验原理。但是作为优化算法,这里也把对应的效果图贴上了。
对于示例图,其最终计算能量矩阵的效果图下:
从图中看,显得略微杂乱,此时我也无法比较这种算法的优点。
到这里,感知内容部分过程就已经结束了。而能量感知部分具体的目标是获取到能量矩阵。
2. 计算需要裁剪(增加)的像素点
当我们通过第二部分得到具体能量矩阵的时候,我们已经对图像的接下来的每个点的像素的重要程度有了一定的认知,接下来需要执行去除部分操作了。
在图像缩放的时候,通常我们都会希望删除的是无关轻重的像素点,因为往往越重要的像素点对于图像的影响越大。
假设我们要对图像处理过程中让图像从640x426变为639x426,即我们要使图像变少一列。同时我们又有了标志图像的重要性的能量矩阵,很容易可以想到,我们只需要删除能量矩阵中每行能量最低的那个点的像素就行了。但是很不幸,这种简单的删除可能会导致画面的撕裂。那么如何删除才能不产生撕裂感呢?
这里也就引申出了 缝(Seam
) 的概念,对于一个缝来说,其必须是连续的线,在图像矩阵中,我们认为某个像素点和其周围的8个像素点,是连续的。对于图像而言,删除缝的撕裂感更低。
这里将上述的条件简化如下:
- 若要删除某个点,则接下来只能删除其临近上、下的三个点(这里以图像缩放列为例),最终删除的所有点组合成缝
- 我们需要删除能量总和最低的那个缝
但是我们又如何去找到能量最低的那个缝呢?
动态规划
算法的重要性就体现出来了,缝的可选组合相当多,如果我们要做暴力计算的话,一个640x426的图像想要缩放成639x426,其包含的缝的个数可能性约为3^426,我们要从这么多可能里面去寻找最低的那个,明显是不现实的。并且该问题明显是可以通过迭代来缩小问题规模的,所以这里使用动态规划来解决可以有效降低计算复杂度。
首先,我们要去考虑,假设我们已经确定了要删除图像中缝的当前点坐标为(i, j),那么我们一定会选择的缝为以其上边三个点为终点的缝的能量最低值的那个缝,此时当前缝的能量为
E(i,j) = min(E(i - 1,j - 1), E(i - 1,j), E(i - 1,j + 1)) + e(i, j)
,由此递归表达式就得到了,我们只需要使用空间记录好每个E(i, j)
的能量值,即可获得所有缝的最低能量值。
这里可以看到,通过计算后,可以得到缝的最低累积能量值矩阵。我们想要具体获得缝,可以使用倒序,同样沿着能量最低矩阵的最低能量路径便可得到具体的缝的路径坐标。
接下来展示的是累积能量矩阵函数图
自定义能量函数
Sabel算子(图像梯度)
Forward Energy
在我们有累积能量图之后,可以更直观的想到,我们的路径其实只需从下往上去找最暗的那条缝就行了。
这里将路径标回到原图中可以得到:
自定义能量函数
Sabel算子(图像梯度)
Forward Energy
其中红线部分为裁剪路径,可以看到,不同算法的裁剪路径均不相同。
3. 执行裁剪
接下来需要做的,就是把上述步骤中标红的点从图像中删除,即可完成一次Seam Carving
缩放操作,接下来我们只需要将裁剪后的图片递归进行操作1-3步,直到图片大小达到要求。
这里将使用gif图片展示具体裁剪过程
自定义能量函数
Sabel算子(图像梯度)
Forward Energy
对比可以看出,自定义算法在前期影响几乎相同。随着图像的继续压缩,效果下降比较明显。
这里只演示了沿着竖轴删除行,若要延横轴进行删除的,可以采取将图像进行90°旋转,随后对旋转后的图像继续按竖轴进行缩放,即可满足要求。
算法原理-放大
对于图像压缩,Seam Carving
的主要目的是删除能量值最低缝
用以保证能量高的部分,即重点区域不受影响。对于图像放大,其原理相同,即尽可能的保证能量高的区域在图像放大过程中维持其原有结构不变化。所以我们需要采取的处理才做为,先采用压缩算法,获取前k条缝(k为需要放大后图像宽度-当前图像宽度),随后我们将图像按照缝的顺序依次拉伸,并采用传统拉伸算法对拉伸点进行像素填充,最终所得图像即为填充像素。
关于放大效果,随着放大的比率的提高,放大效果在逐渐变差(因为哪怕获取前k条缝,希望控制的目标区域也会随着k的增大而产生形变)
自定义能量函数
Sabel算子(图像梯度)
Forward Energy
算法原理-目标区域删除(此处部分为个人猜想,并无实际代码支撑)
在论文中提到了可以对于用户指定的区域进行剔除。此处也可以等价的将其看做,图像可能需要删除包含特定区域的指定的缝。这里个人理解依然可以对 能量函数(能量矩阵) 部分处做相关处理,即结合用户给定区域,对累积能量矩阵的相关位置的能量赋予较大的负值,用于吸引动态规划部分,将此区域的缝设置为最小累积能量的缝。随后结合后续操作,即可在压缩图像的同时完成删除特定区域。
算法总结
Seam Carving算法
做为图像缩放算法,具有如下优点:
- 具有优异的扩展性,其
能量函数
的引入使得后续算法裁剪步骤可以与前面的算法能量函数部分解耦,从而使我们在对裁剪部分的设计时,可以更多的关心能量函数
的实现。 - 具有不错的效果
但同时其也具有明显缺点即对比传统图像拉伸算法,因其处理步骤多,导致其算法效率低下
从设计角度出发Seam Carving算法
展示出了卓越的设计能力,其设计模式符合了接口模式,又将动态规划融合到了路径计算中。
同时,上面这些技巧也是我们在接下来可以借鉴并应用在其他领域的方法。
上述算法支撑的相关代码在github