优秀的编程知识分享平台

网站首页 > 技术文章 正文

如何用Python实现神奇切图算法Seam Carving?

nanyue 2024-07-18 22:14:25 技术文章 7 ℃

在第 34 届 SIGGRAPH 2007 数字图形学年会上,以色列的两位教授 Shai Avidan 和 Ariel Shamir 展示了一种新的缩放裁剪图像方法,利用这个技术我们可以在缩放时固定图片中特定区域的大小,或者可以在缩小时让特定的区块被周围图像缝合消除使图片缩放后仍然维持整体的完整性。

变为:

以所举的例子为素材,重点讲讲如何用Python基本实现接缝剪裁算法。

为了执行缝隙拼接,我们需要两个重要的输入:

· 1.原始图片:我们想要调整大小的图片。

· 2.能量图(energy map): 我们从原始图像导出的能量图。

算法工作过程如下所示:

00001. 为每个像素分配一个能量值

00002. 找到能量值最小的像素的八连通路径

00003. 删除路径中的所有像素

00004. 重复前面1-3步,直到删除的行/列数量达到理想状态

下面是我们需要导入的环境依赖:

import sys

import numpy as npfrom imageio import imread, imwritefrom scipy.ndimage.filters import convolve

# tqdm并非必需,但能为提供很美观的进度条,方便我们查看进度from tqdm import trange

开始计算能量图:

def calc_energy(img):

filter_du = np.array([

[1.0, 2.0, 1.0],

[0.0, 0.0, 0.0],

[-1.0, -2.0, -1.0],

])

# 这会将它从2D滤波转换为3D滤波器

# 为每个通道:R,G,B复制相同的滤波器

filter_du = np.stack([filter_du] * 3, axis=2)

filter_dv = np.array([

[1.0, 0.0, -1.0],

[2.0, 0.0, -2.0],

[1.0, 0.0, -1.0],

])

# 这会将它从2D滤波转换为3D滤波器

# 为每个通道:R,G,B复制相同的滤波器

filter_dv = np.stack([filter_dv] * 3, axis=2)

img = img.astype('float32')

convolved = np.absolute(convolve(img, filter_du)) + np.absolute(convolve(img, filter_dv))

# 我们将红,绿,蓝通道中的能量相加

energy_map = convolved.sum(axis=2)

return energy_map

找到能量值最小的缝隙,创建一个称为 M 的 2D 数组,存储该像素上可见的最小能量值。

def minimum_seam(img):

r, c, _ = img.shape

energy_map = calc_energy(img)

M = energy_map.copy()

backtrack = np.zeros_like(M, dtype=np.int)

for i in range(1, r):

for j in range(0, c):

# 处理图像的左侧边缘,确保我们不会索引-1

if j == 0:

idx = np.argmin(M[i - 1, j:j + 2])

backtrack[i, j] = idx + j

min_energy = M[i - 1, idx + j]

else:

idx = np.argmin(M[i - 1, j - 1:j + 2])

backtrack[i, j] = idx + j - 1

min_energy = M[i - 1, idx + j - 1]

M[i, j] += min_energy

return M, backtrack

从具有最小能量值的缝隙中删除像素

然后我们移除具有最小能量值的缝隙,返回一个新图像:

def carve_column(img):

r, c, _ = img.shape

M, backtrack = minimum_seam(img)

# 创建一个(r,c)矩阵,填充值为True

# 后面会从值为False的图像中移除所有像素

mask = np.ones((r, c), dtype=np.bool)

# 找到M的最后一行中的最小元素的位置

j = np.argmin(M[-1])

for i in reversed(range(r)):

# 标记出需要删除的像素

mask[i, j] = False

j = backtrack[i, j]

# 因为图像有3个通道,我们将蒙版转换为3D

mask = np.stack([mask] * 3, axis=2)

# 删除蒙版中所有标记为False的像素,

# 将其大小重新调整为新图像的维度

img = img[mask].reshape((r, c - 1, 3))

return img

在每一列重复此项操作

def crop_c(img, scale_c):

r, c, _ = img.shape

new_c = int(scale_c * c)

for i in trange(c - new_c): # use range if you don't want to use tqdm

img = carve_column(img)

return img

汇总信息

我们可以添加一个主函数,从如下命令行调用该函数:

def main():

scale = float(sys.argv[1])

in_filename = sys.argv[2]

out_filename = sys.argv[3]

img = imread(in_filename)

out = crop_c(img, scale)

imwrite(out_filename, out)

if __name__ == '__main__':main()

然后用如下代码运行:

python carver.py 0.5 image.jpg cropped.jpg

现在,cropped.jpg 应该包含如下一张图:

这样我们就用 Python 实现了接缝剪裁算法!

那么行呢?

很简单,只需旋转一下图像,运行 crop_c 就 ok 了!

def crop_r(img, scale_r):

img = np.rot90(img, 1, (0, 1))

img = crop_c(img, scale_r)

img = np.rot90(img, 3, (0, 1))

return img

将如下内容添加至主函数,现在我们也能剪裁行了!

def main():

if len(sys.argv) != 5:

print('usage: carver.py <r/c> <scale> <image_in> <image_out>', file=sys.stderr)

sys.exit(1)

which_axis = sys.argv[1]

scale = float(sys.argv[2])

in_filename = sys.argv[3]

out_filename = sys.argv[4]

img = imread(in_filename)

if which_axis == 'r':

out = crop_r(img, scale)

elif which_axis == 'c':

out = crop_c(img, scale)

else:

print('usage: carver.py <r/c> <scale> <image_in> <image_out>', file=sys.stderr)

sys.exit(1)

imwrite(out_filename, out)

以如下代码运行:

python carver.py r 0.5 image2.jpg cropped.jpg

学会了吗,喜欢的话帮小编评论加关注哦谢谢啦,更多精彩内容请关注百战程序员!

最近发表
标签列表