大哥,帮我P个图呗
忙着呢,没空
很简单的,你看就是这个图他宽了点,放到页面上人都挤扁了,帮稍微P窄一点点就行了
那你把图片裁掉一点不就好了
裁掉人就看不全了
好吧,发我邮箱有空帮你弄下
复制代码
为了更好的显示效果,咱们常常须要调整一下图片尺寸,而PS技术又不熟练,看完这篇文章,你就不用求着设计师帮你P图了java
Seam carving算法是一种有趣的图像缩放算法,和常见的外框裁剪或者几何拉伸不一样,算法能感知到图片内容,区分出主要的物体,避开这些主体的基础上进行变形的,例如:python
能够看到图2虽然宽度缩小了,可是其中的人和建筑并无明显的变形,而3中的人和建筑已经被压扁了,4中建筑已经不完整了。人和建筑做为图片中主要元素,被算法感知到,并在处理时被尽可能的保留了下来。算法
固然,算法也能够应用到垂直方向上,或者同时应用到水平和垂直两个方向,例如: 函数
甚至,能够将图片中某个物体标记出来,定向的移除这个物体,效果几乎能够媲美专业的PS。找找看,下面哪只鞋子不见了 布局
若是没找到,不要紧,文章最后提供了参考答案
复制代码
2007年,Shai Avidan和Ariel Shamir发表的《Seam Carving for Content-Aware Image Resizing》中首次提出Seam carving算法,文中提到,图片的排版布局形式多样,同一张图片每每须要不少不一样的尺寸来适应不一样的场景和设备,而仅仅是外框裁剪亦或是简单的几何缩放效果都不是很好,因此须要一种优雅的方式来动态的调整图片的尺寸,而这种尺寸的变换又须要能很好的保留图片想要表达的信息,这就是Seam carving算法。优化
为了使变换结果尽量的天然,须要找出图片中不重要的,包含信息量少的像素,论文中用的描述是unnoticeable pixels that blend with their surroundings
。 这里定义了一个能量函数的概念,文中给出了能量函数以下:spa
能量越大,意味着包含的信息越多,对图片进行变换时须要尽量避开能量大的像素设计
算法的逻辑是这样的,假设须要将图片的宽度从600裁剪到400。先找出一条竖直方向上的夹缝,从图片中移除这条夹缝,宽度从600减为599,重复200次,便可到一张宽度400的新图。3d
上面提到的一条竖直方向上的夹缝,有以下要求,code
为了找到一条最小能量的夹缝(Seam),从最上方第一行开始,向下列出全部可能的夹缝路线,最后找到能量损失最小的那条,其中位于(i, j)坐标处的能量损失定义以下:
找出夹缝后,将夹缝移除,图片宽度-=1;
而后继续计算新的图片中的能量和能量损失,找出下一条损失最小的夹缝,一直减小图片宽度,直到图片宽度知足要求
下面展现下裁剪图片的具体流程:
与裁剪相似,图片拉伸也是在能量最小的像素上对图片进行操做,找出能量损失最小的夹缝,将夹缝复制插入原先位置,循环往复,便可实现图片的拉伸。
须要注意的是,与裁剪操做每次循环操做一条夹缝不一样,拉伸操做须要一次性找出全部待插入的夹缝(取能量损失从小到大前size条),批量总体插入夹缝,不然每次循环将会找到同一条夹缝,并不断的重复插入。
为了定向的移除图片中的物体,只需在energy_function计算后将物体对应的像素的能量值手动调小,这样一来找到的夹缝天然会通过定向的物体,循环数次以后,待移除的物体就从图片中被移除了。这时再使用拉伸操做将图片拉回原始尺寸,物体的移除操做就完成了
这里还有一个问题,能量函数为何是梯度的距离。
贴心的做者在文中给出了解释:
We have tested both and
of the gradient, saliency measure [Itti et al. 1999], and Harris-corners measure [Harris and Stephens 1988]. We also used eye gaze measurement [DeCarlo and Santella 2002], and the output of face detectors.
在比对了一番后,得出结论,没有哪个能适合全部场景,可是总的来讲, 和
这两个表现的不错,其中
定义以下:
由于每一条夹缝都须要从新计算能量和能量损失,这个算法的计算量仍是比较大的,上面图片从640裁剪到400,在个人笔记本上平均执行6s左右。
考虑到夹缝移除后,其余部分的能量是不会变化的,其实仅需更新移除附近的能量值,按照这个思路,优化后大概减伤了0.5s的计算时间,可是整个计算时间仍是偏长。
由于平时java用的比较顺手,就用java从新撸了一遍,处理同一张图片时间减小到了1s左右,效率上也不是很理想,但愿后面能想办法再优化一下效率。
最后,附上完整的python代码,其中:
reduce(image, size)方法提供了图片的裁剪
enlarge(image, size)方法提供了图片的拉伸
remove_object(image, mask)方法提供了物体移除
energy_function(image)实现的是,有兴趣的朋友能够尝试下其余能量函数
python 3.7.3
import numpy as np
import matplotlib.pyplot as plt
from skimage import color, io, util
from time import time
def energy_function(image):
gray_image = color.rgb2gray(image)
gradient = np.gradient(gray_image)
return np.absolute(gradient[0]) + np.absolute(gradient[1])
def compute_cost(image, energy, axis=1):
energy = energy.copy()
if axis == 0:
energy = np.transpose(energy, (1, 0))
H, W = energy.shape
cost = np.zeros((H, W))
paths = np.zeros((H, W), dtype=np.int)
# Initialization
cost[0] = energy[0]
paths[0] = 0
for row in range(1, H):
upL = np.insert(cost[row - 1, 0:W - 1], 0, 1e10, axis=0)
upM = cost[row - 1, :]
upR = np.insert(cost[row - 1, 1:W], W - 1, 1e10, axis=0)
upchoices = np.concatenate((upL, upM, upR), axis=0).reshape(3, -1)
# M(i, j) = e(i, j) + min(M(i -1 , j - 1), M(i - 1, j), M(i - 1, j + 1))
cost[row] = energy[row] + np.min(upchoices, axis=0)
# left = -1
# middle = 0
# right = 1
paths[row] = np.argmin(upchoices, axis=0) - 1
if axis == 0:
cost = np.transpose(cost, (1, 0))
paths = np.transpose(paths, (1, 0))
return cost, paths
def backtrack_seam(paths, end):
H, W = paths.shape
seam = - np.ones(H, dtype=np.int)
seam[H - 1] = end
for h in range(H - 1, 0, -1):
seam[h - 1] = seam[h] + paths[h, end]
end += paths[h, end]
return seam
def remove_seam(image, seam):
if len(image.shape) == 2:
image = np.expand_dims(image, axis=2)
H, W, C = image.shape
mask = np.ones_like(image, bool)
for h in range(H):
mask[h, seam[h]] = False
out = image[mask].reshape(H, W - 1, C)
out = np.squeeze(out)
return out
def reduce(image, size, axis=1, efunc=energy_function, cfunc=compute_cost):
out = np.copy(image)
if axis == 0:
out = np.transpose(out, (1, 0, 2))
while out.shape[1] > size:
energy = efunc(out)
costs, paths = cfunc(out, energy)
end = np.argmin(costs[-1])
seam = backtrack_seam(paths, end)
out = remove_seam(out, seam)
if axis == 0:
out = np.transpose(out, (1, 0, 2))
return out
def duplicate_seam(image, seam):
if len(image.shape) == 2:
image = np.expand_dims(image, axis=2)
H, W, C = image.shape
out = np.zeros((H, W + 1, C))
for h in range(H):
out[h] = np.vstack((image[h, :seam[h]], image[h, seam[h]], image[h, seam[h]:]))
return out
def find_seams(image, k, axis=1, efunc=energy_function, cfunc=compute_cost):
image = np.copy(image)
if axis == 0:
image = np.transpose(image, (1, 0, 2))
H, W, C = image.shape
indices = np.tile(range(W), (H, 1))
seams = np.zeros((H, W), dtype=np.int)
for i in range(k):
# Get the current optimal seam
energy = efunc(image)
cost, paths = cfunc(image, energy)
end = np.argmin(cost[H - 1])
seam = backtrack_seam(paths, end)
# Remove that seam from the image
image = remove_seam(image, seam)
# Store the new seam with value i+1 in the image
seams[np.arange(H), indices[np.arange(H), seam]] = i + 1
# Remove the indices used by the seam, so that `indices` keep the same shape as `image`
indices = remove_seam(indices, seam)
if axis == 0:
seams = np.transpose(seams, (1, 0))
return seams
def enlarge(image, size, axis=1, efunc=energy_function, cfunc=compute_cost):
out = np.copy(image)
if axis == 0:
out = np.transpose(out, (1, 0, 2))
H, W, C = out.shape
seams = find_seams(out, size - W)
for i in range(size - W):
seam = np.where(seams == i + 1)[1]
out = duplicate_seam(out, seam)
if axis == 0:
out = np.transpose(out, (1, 0, 2))
return out
def remove_object(image, mask):
assert image.shape[:2] == mask.shape
H, W, _ = image.shape
out = np.copy(image)
H,W,C = out.shape
while not np.all(mask == 0):
energy = energy_function(out)
weighted_energy = energy + mask * (-100)
cost, paths = compute_cost(out, weighted_energy)
end = np.argmin(cost[-1])
seam = backtrack_seam(paths, end)
out = remove_seam(out, seam)
mask = remove_seam(mask,seam)
return enlarge(out, W, axis=1)
tower = io.imread('imgs/tower_original.jpg')
tower = util.img_as_float(tower)
plt.subplot(1, 2, 1)
plt.imshow(tower)
out = reduce(tower, 400)
plt.subplot(1, 2, 2)
plt.imshow(out)
plt.show()
复制代码
最后参考答案,左上角的是原图,其余三张图分别移除了一只鞋子