近日遇到一批类似下图的商品图,图片中主要内容(相机)占区域太小,周围的白色背景区域太大,我们希望通过裁剪,让内容尽量铺满图片,减少周围的留白。
因为中间有内容的区域,大小是不固定的,所以不能使用按统一比例裁剪。例如下面两幅图:
按照常规的方法,我们可以遍历图片中的每一个像素点,找到位于最上、最右、最下、最左的4个非白色的像素点,从而算出裁切的位置。但是这样做存在两个问题:
- 遍历所有像素点,如果图片很大,耗时会很久。
- 如果白色留白的部分存在一些噪点,会影响到裁切位置。
我采用了一个折中的思路,只检查图片的中线和对角线上的点,如下图蓝色轨迹所示,通过遍历蓝色轨迹,确定图中红色圈出的 6 个点,最后通过这 6 个点确定裁切位置。
这样做的好处是:
- 计算量非常少,只需要一个循环。
- 假设图中 A 位置有一个噪点,也不会影响。
不过这样做也会有不足之处,如果噪点位于蓝色轨迹上,就无法避免。另外,如果图片内容不是在正中,而是在下图中红色区域(蓝色轨迹和内容无法确定 6 个点),就会影响裁剪的结果。我觉得对于后者,大家可以在我的代码上做些改进。
这是示例图的裁切效果:
代码:
from PIL import Image, ImageDraw
import numpy as np
import os
imgDir = r"./img" # 原始图片目录
side = 512 # 输出边长
def min_not_zero(data):
new_data = [x for x in data if x != 0]
return min(new_data if len(new_data) != 0 else [0])
i = 0
for file in os.listdir(imgDir):
print(file)
if not (file.endswith('.jpg') or file.endswith('.png')):
continue
path = os.path.join(imgDir, file)
img = Image.open(path)
imgArr = np.array(img)
height = imgArr.shape[0]
width = imgArr.shape[1]
gap = width * 0.02
draw = ImageDraw.Draw(img)
xStart = [width, width, width, width]
yStart = [height, height, height, height]
xEnd = [0, 0, 0, 0]
yEnd = [0, 0, 0, 0]
i += 1
length = min(width, height)
for x in range(length):
y = x # 正方形图片可以直接相等
# 先简单处理非正方形
if x > height:
y = height - 1
# 左上到右下
if imgArr[y][x][0] < 255 or imgArr[y][x][1] < 255 or imgArr[y][x][2] < 255:
if xStart[0] > x:
xStart[0] = x
if yStart[0] > y:
yStart[0] = y
if x > xEnd[0]:
xEnd[0] = x
if y > yEnd[0]:
yEnd[0] = y
# 左下到右上
if imgArr[height - y - 1][x][0] < 255 or imgArr[height - y - 1][x][1] < 255 or imgArr[height - y - 1][x][2] < 255:
if xStart[1] > x:
xStart[1] = x
if yStart[1] > height - y:
yStart[1] = height - y
if x > xEnd[1]:
xEnd[1] = x
if yEnd[1] == 0:
yEnd[1] = height - y
# 中横线
if imgArr[int(height / 2)][x][0] < 255 or imgArr[int(height / 2)][x][1] < 255 or imgArr[int(height / 2)][x][2] < 255:
if xStart[2] > x:
xStart[2] = x
if x > xEnd[2]:
xEnd[2] = x
# 中竖线
if imgArr[y][int(width / 2)][0] < 255 or imgArr[y][int(width / 2)][1] < 255 or imgArr[y][int(width / 2)][2] < 255:
if yStart[3] > y:
yStart[3] = y
if y > yEnd[3]:
yEnd[3] = y
xS = min_not_zero(xStart)
xE = max(xEnd)
yS = min_not_zero(yStart)
yE = max(yEnd)
if xS == width:
xS = 0
if yS == height:
yS = 0
if xE == 0:
xE = width
if yE == 0:
yE = height
# 裁切正方形,取 x y 并集
start = min(xS, yS)
end = max(xE, yE)
xS = start
yS = start
xE = end
yE = end
cropped = img.crop((xS - (gap if xS > gap else xS), yS - (gap if yS > gap else yS), xE + (gap if width - xE > gap else width - xE), yE + (gap if height - yE > gap else height - yE))).resize((side, side))
cropped.save(r"./temp/{}.jpg".format(file[:-4], i)) # 输出图片
您的赞助将会支持作者创作及本站运维
发表评论