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--让图片比例随心缩放

Image Retargeting - 图像缩略图 图像重定向的更多相关文章

  1. PHP图像裁剪为任意大小的图像,图像不变形,不留下空白

    <?php /** * 说明:函数功能是把一个图像裁剪为任意大小的图像,图像不变形 * 参数说明:输入 需要处理图片的 文件名,生成新图片的保存文件名,生成新图片的宽,生成新图片的高 */ fu ...

  2. Atitti 图像处理 图像混合 图像叠加 blend 原理与实现

    Atitti 图像处理 图像混合 图像叠加 blend 原理与实现 混合模式 编辑 本词条缺少信息栏,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧! 混合模式是图像处理技术中的一个技术名词,不 ...

  3. 本图片处理类功能非常之强大可以实现几乎所有WEB开发中对图像的处理功能都集成了,包括有缩放图像、切割图像、图像类型转换、彩色转黑白、文字水印、图片水印等功能

    import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphic ...

  4. 图像热点&图像映射

    图像映射 图像映射也称为图像热点. 作用: 让同一张图片上的不同区域,可以实现多个不同的超链接功能. 图示: <map>图像映射三步走: 图像映射的实现需要三方面配合完成: 1.图像映射容 ...

  5. opencv 3 core组件进阶(2 ROI区域图像叠加&图像混合;分离颜色通道、多通道图像混合;图像对比度,亮度值调整)

    ROI区域图像叠加&图像混合 #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp&g ...

  6. RGB-D(深度图像) & 图像深度

    RGB-D(深度图像)   深度图像 = 普通的RGB三通道彩色图像 + Depth Map   在3D计算机图形中,Depth Map(深度图)是包含与视点的场景对象的表面的距离有关的信息的图像或图 ...

  7. HTML5新特性之Canvas+drag(拖拽图像实现图像反转)

    1.什么是canvas 在网页上使用canvas元素时,会创建一块矩形区域,默认矩形区域宽度300px,高度150px.. 页面中加入canvas元素后,可以通过javascript自由控制.可以在其 ...

  8. CSS 背景图像 重复图像

    重复图像 background-repeat 属性可以重复图像,这对于小图片来说是福音. background-repeat 属性有6个值: repeat 背景图像在垂直方向和水平方向都重复 repe ...

  9. 机器学习进阶-案例实战-图像全景拼接-图像全景拼接(RANSCA) 1.sift.detectAndComputer(获得sift图像关键点) 2.cv2.findHomography(计算单应性矩阵H) 3.cv2.warpPerspective(获得单应性变化后的图像) 4.cv2.line(对关键点位置进行连线画图)

    1. sift.detectAndComputer(gray, None)  # 计算出图像的关键点和sift特征向量 参数说明:gray表示输入的图片 2.cv2.findHomography(kp ...

随机推荐

  1. 序列化表单数据$("form").serializeArray()

    在做一个后台管理系统类似的项目中发现不能直接取得表单中的数值,于是想到先将表单数据转化为json,然后再用js从json中获取数据,那不就简单了吗? 于是我用了jquery的serializeArra ...

  2. vue文章学习路线

    vue学习笔记(一)入门 Vue实现简单的购物车功能 vue学习笔记(二)vue的生命周期和钩子函数 使用webstorm搭建vue-cli项目 vue-cli项目中引入第三方插件 vue-cli项目 ...

  3. jquery简单实现复选框的全选与反选

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  4. Linux中编写shell脚本的小例子

    1.创建一个test.sh的文件 touch test.sh 2.编辑这个文件 vi test.sh 3.进入到编辑页面后将编辑页面先切换成编辑模式(按Esc键就可以切换) 4.切换到编辑模式后开始向 ...

  5. spring boot 中事物的使用

    一.什么是事务? 事务,通俗的说就是,同时做多个事,要么全做,要么不做,也是其特性.举个例子来说,好比你在某宝.某东.某多上购物,在你提交订单的时候,库存也会相应减少,不可能是钱付了,库存不减少,或者 ...

  6. 洛谷P1248 加工生产调度 贪心

    正解:贪心 解题报告: 传送门$QwQ$ $umm$直接看可能比较难想,可以先考虑另一个题? 有$n$个小怪,每打一只小怪会扣$a_i$的血,打完之后会回升$b_i$的血,问至少要多少血量才能使全程血 ...

  7. idea编辑器的使用

    编辑器下载和安装就不说了,网上每次版本都更换得好快 ,发新版的人很多idea2019:https://pan.baidu.com/s/1zc1wkQLLVxbXSjy4ISN4aQ 提取码:cgah, ...

  8. 1053 住房空置率 (20 分)C语言

    在不打扰居民的前提下,统计住房空置率的一种方法是根据每户用电量的连续变化规律进行判断.判断方法如下: 在观察期内,若存在超过一半的日子用电量低于某给定的阈值 e,则该住房为"可能空置&quo ...

  9. SpringBootTest 测试工具

    以下内容,翻译自官方文档,并结合了学习过程的demo. Spring Boot提供了许多实用程序和注解,帮助测试应用程序.测试支持由两个模块提供:spring-boot-test 包含核心项,spri ...

  10. Spring Boot中利用递归算法查询到所有下级用户,并手动进行分页

    Spring Boot中利用递归算法查询到所有下级用户,并手动进行分页 前提:语言用的是kotlin(和Java一样,但更简洁),写下这篇文章用来记录编程过程中遇到的一些难点 1.功能需求 前端用户A ...