大哥,幫我P個圖唄
忙着呢,沒空
很簡單的,你看就是這個圖他寬了點,放到頁面上人都擠扁了,幫稍微P窄一點點就行了
那你把圖片裁掉一點不就好了
裁掉人就看不全了
好吧,發我郵箱有空幫你弄下
複製代碼
爲了更好的顯示效果,咱們常常須要調整一下圖片尺寸,而PS技術又不熟練,看完這篇文章,你就不用求着設計師幫你P圖了java
Seam carving算法是一種有趣的圖像縮放算法,和常見的外框裁剪或者幾何拉伸不一樣,算法能感知到圖片內容,區分出主要的物體,避開這些主體的基礎上進行變形的,例如:python
上面,1是原圖,2是Seam-carving算法縮放後的圖片,3是通過簡單壓縮尺寸獲得的圖片,4是按目標尺寸裁剪後的圖片。能夠看到圖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()
複製代碼
最後參考答案,左上角的是原圖,其餘三張圖分別移除了一隻鞋子