Image Retargeting - 圖像縮略圖 圖像重定向


Image Retargeting

圖像縮略圖、圖像重定向

前言

這篇文章主要對比DL出現之前的幾種上古算法,為了作為DL方法的引子而存在,順便博客也該更新點新內容上來了,這篇博文就是介紹了我最近在玩什么。

本文方法

傳統的方法主要有三種:Resize拉伸、收縮)、Crop裁剪)和Seam Carving接縫裁剪)。

其中接縫裁剪這個算法挺好玩的,論文參見 Seam Carving,截止本篇博文,被引用次數是1914次,可以說是很經典的文章了。

該論文實現的效果圖:

本文用到的python庫

三種算法的對比由python實現,python版本為python3.8,對應下列依賴庫版本為conda直接安裝,不同版本請注意自己改動部分接口。

opencv 用於圖像處理
scipy 用於圖像卷積
notebook 提供環境
matplotlib 用於圖像顯示
tqdm 用於進度顯示(可不用 主要是因為SC算法太慢了 會讓人覺得程序卡了
numpy 用於輔助opencv

具體引用代碼如下:

import cv2
import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage.filters import convolve
from tqdm import trange

圖像的讀入

都有opencv了,還用問么?

img = cv2.imread('test1.jpg')
imshow(img)
img.shape

圖像的顯示

其中imshow()函數是自己定義的,用於顯示處理結果和處理過程的中間圖像,這樣就方便在notebook中查看了,需要注意的是opencv存儲圖像的格式和PIL不太一樣,為bgr,需要轉換。

def imshow(img):
    if (len(img.shape) == 2) :
        plt.imshow(img)
        plt.show()
        return
    b,g,r = cv2.split(img) 
    img_rgb = cv2.merge([r,g,b]) 
    plt.imshow(img_rgb)
    plt.show()

方法一:裁剪(Crop)

裁剪配合numpy的花式索引(別笑,這是正式名稱)即可實現,本質上就是對數組的划分。

假如限定屏幕寬度為900像素(因為一般用在手機、iPad等終端上,所以不限制高度),Resize的結果如下:

左側裁剪:

width = 900
height = img.shape[0]
crop = img[:height, :width]
imshow(crop)

居中裁剪:

width = 900
height = img.shape[0]
crop = img[:height, (img.shape[1] - width) // 2 : (img.shape[1] + width) // 2]
imshow(crop)

可以看出,裁剪方法完全沒有考慮圖像的細節,簡單的裁剪帶來內容的嚴重丟失,優點是速度極快,幾乎不消耗資源。

方法二:縮放(Resize)

縮放也是使用opencv內置函數實現。

opencv提供了五種Resize方法:

INTER_NEAREST - 最鄰近插值
INTER_LINEAR - 雙線性插值 默認
INTER_AREA - resampling using pixel area relation.
INTER_CUBIC - 4x4像素鄰域內的雙立方插值
INTER_LANCZOS4 - 8x8像素鄰域內的Lanczos插值

width = 900
height = 600
resize = cv2.resize(img, (width,height))
imshow(resize)

可以看出,縮放方法造成了圖像的失真,而且是嚴重失真,其優點也是速度極快,幾乎不消耗資源。

方法三:接縫裁剪(Seam Carving)

這是本文重點介紹的算法,主要思想是圖像總有一些不重要的列,將其刪除比刪除隨機的列或者重新填充要更保留圖像的細節部分,同時確保圖像整體不嚴重失真(這里的列不是數組意義上的列,是圖像中八聯通的一條線,即一條接縫)。

步驟一:獲取圖像的能量圖:

能量圖就是圖像的邊緣啦,相當於圖像的細節,這里使用偷懶的卷積實現。

卷積核是這兩個:

def cal_energy(img):
    filter_du = np.array([
        [1.0, 2.0, 1.0],
        [0.0, 0.0, 0.0],
        [-1.0, -2.0, -1.0],
    ])

    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],
    ])

    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

energy_map = cal_energy(img)
print(energy_map.shape)
imshow(energy_map)

卷積核是兩個,分別從行和列上進行卷積操作。

這里是用了偷懶的卷積操作,對圖像所有像素點做卷積運算,相當於如下C艹代碼:

Mat compute_score_matrix(Mat energy_matrix)
{
	Mat score_matrix = Mat::zeros(energy_matrix.size(), CV_32F);
	score_matrix.row(0) = energy_matrix.row(0);

	for (int i = 1; i < score_matrix.rows; i++)
	{
		for (int j = 0; j < score_matrix.cols; j++)
		{
			float min_score = 0;

			// Handle the edge cases
			if (j - 1 < 0)
			{
				std::vector<float> scores(2);
				scores[0] = score_matrix.at<float>(i - 1, j);
				scores[1] = score_matrix.at<float>(i - 1, j + 1);
				min_score = *std::min_element(std::begin(scores), std::end(scores));
			}
			else if (j + 1 >= score_matrix.cols)
			{
				std::vector<float> scores(2);
				scores[0] = score_matrix.at<float>(i - 1, j - 1);
				scores[1] = score_matrix.at<float>(i - 1, j);
				min_score = *std::min_element(std::begin(scores), std::end(scores));
			}	
			else
			{
				std::vector<float> scores(3);
				scores[0] = score_matrix.at<float>(i - 1, j - 1);
				scores[1] = score_matrix.at<float>(i - 1, j);
				scores[2] = score_matrix.at<float>(i - 1, j + 1);
				min_score = *std::min_element(std::begin(scores), std::end(scores));
			}
						
			score_matrix.at<float>(i, j) = energy_matrix.at<float>(i, j) + min_score;
		}
	}

	return score_matrix;
}

卷積之后的圖像即為願圖像的能量圖,代表了圖像的細節部分,即更鋒利的邊緣,該算法認為平坦的部分能量更低,自己實驗一下就能明白,一方面有效保留了圖像中的細節部分,另一方面可能造成算法錯誤的刪除了圖像的重要部分,如雪白平坦的胸部等。

步驟二:獲取圖像接縫

圖像的接縫就是一個八聯通的線,每行有且只能選取一個像素,這里使用動態規划,回溯法求解,dp轉移方程如下:

M(i, j) = e(i, j) + min{M(i - 1, j - 1), M(i - 1, j), M(i - 1, j + 1)}

def minimum_seam(img):
    r, c, _ = img.shape
    energy_map = cal_energy(img)

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

    for i in range(1, r):
        for j in range(c):
            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
M, backtrack = minimum_seam(img)
imshow(M)

圖像的接縫由dp求出,可以看出這個算法是十分慢的,同時因為損失最小的接縫被刪掉后,該接縫涉及到的左右兩側的損失不能直接復用,必須重新計算,進一步減慢了算法的執行速度。

步驟三:裁剪一列

接縫都求出來了,很明顯裁剪的那一列就應該是損失最小的接縫,刪除方法使用numpy的黑科技argmin()。

def carve_column(img):
    r, c, _ = img.shape

    M, backtrack = minimum_seam(img)

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

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

    for i in reversed(range(r)):
        mask[i, j] = False
        j = backtrack[i, j]

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

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

    return img
for i in trange(100):
    one = carve_column(img)
imshow(one)

這里模擬刪除圖像中100列之后的情況。

最終步驟:按需裁剪圖像

這里把函數參數改為縮放倍數,其實也可以寫為刪除列數,都一樣,符合人類直覺即可。

def crop_c(img, scale_c):
    r, c, _ = img.shape
    new_c = int(scale_c * c)

    for i in trange(c - new_c):
        img = carve_column(img)

    return img
crop = crop_c(img, 0.8)
imshow(crop)

注意這張圖沒使用原尺寸進行運算,6小時實在難等。

6小時之后更新的圖片,縮小了20%。

可以看到,原圖像在被接縫裁剪后,保留了本身的細節,未引入大面積失真,缺點是慢!慢!慢!測試圖像是一個4K的圖像,運算刪除一列需要30s,刪除20%的列就是768列,總計用時6小時!這樣處理圖片的速度估計沒人可以接受吧。

拓展:裁剪圖像的行

很明確了,翻轉一下行不就變成列了,復用一下就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
crop = crop_r(img, 0.8)
imshow(crop)

圖像效果,運行了三個小時。

拓展:目標移除

理解了原算法之后這就很容易理解了,將能量圖中需要重點保留的東西能量加高,需要刪除的東西能量減低,利用蒙版(mask)即可快速實現目標移除的效果,這里直接貼原論文的效果圖嘍。

后言

根據保密協定,DL部分代碼暫不貼出,我才不會說我還沒看懂呢(

引用

Image-Processing-OpenCV
Implementing Seam Carving with Python
Seam carving--讓圖片比例隨心縮放


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM