手动裁剪数百张图像?不用了!使用 Python 和 OpenCV,您可以自动检测图像的焦点并在几秒钟内将其裁剪为完美的 16:9。本指南向您展示如何节省数小时的编辑时间 - 只需几行代码,您就可以开始了!
这项工作涉及许多步骤,我尽量将其自动化。今天我想告诉您一个特别容易用 Python 自动化的步骤。也就是裁剪图像。
假设我们有这张图片:
我想要的是一张以骑手为焦点、保留图像宽度的 16:9 图像。这个过程很复杂,您可以使用昂贵的解决方案,比如 Photoshop,或者 Krita、Paint.NET 或 IrfanView 来处理。
但是如果您有 1000 个文件需要处理呢?1000x5 分钟 = 大量时间。
所以让我们自动化它。我的最初想法如下:
使用自动裁剪,在固定位置裁剪:
完美!
我的下一个想法必须超越可能性,因为我不知道如何实现:
-
找到图像中最重要的部分在哪里
-
围绕它裁剪 16:9
后者很容易,前者不是吗?但是有 Python、numpy、OpenCV、PIL,为什么不试试呢?
所需库:
-
OpenCV:这个库处理图像处理,并且是我们操纵图像的主要工具。使用
pip install opencv-python
安装它。 -
PIL(或 Pillow):Python Imaging Library 用于额外的图像处理任务。使用
pip install pillow
安装它。 -
NumPy:一个强大的用于数值计算的库,与 OpenCV 完美集成。使用
pip install numpy
安装它。
基本上是免费的 Photoshop :)
经过一些研究,我发现您可以通过模糊处理、灰度化、检测边缘、查找轮廓和识别最大轮廓来实现。
让我们来看看代码中最重要的几行,最终的脚本将在本文末尾:
cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
这行将图像转换为灰度:
接下来,我们必须稍微模糊化图像,以减少后续处理的 CPU 强度:
blurred = cv2.GaussianBlur(gray, (11, 11), 0)
这里需要注意的是,通过增加 (11, 11) 可以增加模糊度,使脚本在查找图像中心时更准确或更不准确。但这些数字必须是奇数。
接下来,我们检测边缘:
edges = cv2.Canny(blurred, 50, 150)
现在,我们找到轮廓。轮廓就像是由边缘形成的形状。所以您可以想象三角形有 3 条边形成一个轮廓:
cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
当我们有了轮廓,我们只需获取最大的轮廓,令人惊讶的是:
是的,最长的轮廓在马腿之间。所以我们画一个边界矩形:
瞧 - 图像中最重要的元素(某种程度上)。
我还将其裁剪为 16:9 并使用三分法则,但这只是数学问题,所以我不会详细介绍。基本上,您取主要主题的中心,围绕它绘制一个 16:9 的矩形,并裁剪图像,同时将其与摄影三分法则对齐,使其看起来令人印象深刻。
同样,我的Python Deck聚焦于 Python 的基本概念 - 捕捉最重要的“中心”,然后精确地定位每个原则,以便您可以有效地学习。就像构图一样,关键是将 Python 最强大的思想放在产生最大影响的地方。
仅供参考,这是两个三分之二的样子:
三分法则是一种摄影指导原则,将图像分为 3x3 网格,帮助您沿着这些线或它们的交点放置重要元素。通过将主要主题与这些点对齐,您可以创造出一个平衡、视觉上吸引人的构图,自然地吸引观众的目光。
以及计算出的裁剪区域:
结果:
漂亮对吧?
如果我们尝试在其他图像上运行它,我们会得到不同的结果:
并不是总是按您想象的那样有效,因为最重要的部分不清楚。这只是一个数学假设:
但它效果相当不错,尤其是当您调整数字时。因此,如果您有很多文件并希望自动裁剪它们 - 保持摄影三分法则并专注于最重要的部分 - 这是完全可行的。
如果您喜欢能够提高工作效率的技术工具,请查看我的桌垫。它们充满了编程快捷方式和提示,旨在让您随手可得地保留基本要点 - 就像这个脚本对图像裁剪所做的那样!
啊,这就是脚本:
import cv2
from PIL import Image
import numpy as np
from pathlib import Path
# 定义源文件夹和目标文件夹
input_folder = "demo" # 图像源文件夹
output_folder = "demo/output" # 处理后图像的目标文件夹```markdown
def crop_important_region(image_path, output_path):
# 加载图像
image = cv2.imread(str(image_path))
if image is None:
raise ValueError(f"无法加载图像:{image_path}")
# 设置保存步骤的文件名前缀
base_name = image_path.stem # 不带扩展名的基本名称
# 步骤 1-1:保存原始图像
cv2.imwrite(str(output_path / f"{base_name}_1-1-original.jpg"), image)
# 获取图像尺寸
img_height, img_width = image.shape[:2]
# 根据图像宽度计算16:9宽高比的高度
target_height = int(img_width * 9 / 16)
# 步骤 1-2:转换为灰度并保存
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imwrite(str(output_path / f"{base_name}_1-2-grayscale.jpg"), gray)
# 步骤 1-3:应用更强的高斯模糊以减少噪点和细节,并保存
blurred = cv2.GaussianBlur(gray, (11, 11), 0) # 增加内核大小以加强模糊效果
cv2.imwrite(str(output_path / f"{base_name}_1-3-blurred.jpg"), blurred)
# 步骤 1-4:使用Canny边缘检测器检测边缘并保存
edges = cv2.Canny(blurred, 50, 150)
edges_colored = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR) # 将边缘转换为BGR以便更好地可视化
edges_colored[np.where((edges_colored == [255, 255, 255]).all(axis=2))] = [0, 0, 255] # 使边缘呈红色
cv2.imwrite(str(output_path / f"{base_name}_1-4-edges.jpg"), edges_colored)
# 步骤 1-5:查找轮廓
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 找到最大的轮廓(假设为主要对象)
largest_contour = max(contours, key=cv2.contourArea)
# 在原始图像上仅可视化最大边缘,以更清晰地显示高分辨率
largest_edge_image = image.copy()
cv2.drawContours(largest_edge_image, [largest_contour], -1, (0, 255, 0), 10) # 绿色,10像素厚度
cv2.imwrite(str(output_path / f"{base_name}_1-5-largest-edge.jpg"), largest_edge_image)
# 获取围绕最大轮廓的边界框
x, y, w, h = cv2.boundingRect(largest_contour)
# 计算最大轮廓的中心
center_x = x + w // 2
center_y = y + h // 2
# 在原始图像上可视化边界框,填充为蓝色,并在中心处画交叉线
bounding_box_image = image.copy()
cv2.rectangle(bounding_box_image, (x, y), (x + w, y + h), (255, 0, 0), -1) # 用蓝色填充边界框
cv2.line(bounding_box_image, (center_x, y), (center_x, y + h), (255, 0, 0), 10) # 中心垂直线
cv2.line(bounding_box_image, (x, center_y), (x + w, center_y), (255, 0, 0), 10) # 中心水平线
cv2.imwrite(str(output_path / f"{base_name}_1-6-bounding-box.jpg"), bounding_box_image)
# 步骤 1-7:绘制三分之一规则网格并显示两分之一叠加
thirds_image = image.copy()
# 绘制垂直三分之一线(黄色,更粗的线以便查看)
cv2.line(thirds_image, (img_width // 3, 0), (img_width // 3, img_height), (0, 255, 255), 8)
cv2.line(thirds_image, (2 * img_width // 3, 0), (2 * img_width // 3, img_height), (0, 255, 255), 8)
# 绘制水平三分之一线
cv2.line(thirds_image, (0, img_height // 3), (img_width, img_height // 3), (0, 255, 255), 8)
cv2.line(thirds_image, (0, 2 * img_height // 3), (img_width, 2 * img_height // 3), (0, 255, 255), 8)
# 用半透明绿色叠加图像的三分之二
overlay = thirds_image.copy()
cv2.rectangle(overlay, (0, 0), (img_width, int(2 * img_height // 3)), (0, 255, 0), -1)
cv2.addWeighted(overlay, 0.3, thirds_image, 0.7, 0, thirds_image)
cv2.imwrite(str(output_path / f"{base_name}_1-7-thirds-and-two-thirds.jpg"), thirds_image)
# 计算裁剪的y坐标,使重要区域居中在16:9裁剪的下部三分之一内
crop_y = max(0, center_y - (2 * target_height) // 3)
crop_y = min(crop_y, img_height - target_height) # 确保裁剪在图像范围内
# 步骤 1-8:在原始图像上显示裁剪区域
crop_area_image = image.copy()
cv2.rectangle(crop_area_image, (0, crop_y), (img_width, crop_y + target_height), (255, 0, 255), 8) # 品红色边框
cv2.imwrite(str(output_path / f"{base_name}_1-8-crop-area.jpg"), crop_area_image)
# 步骤 1-9:将图像裁剪到指定宽度和目标高度
cropped_image = image[crop_y:crop_y + target_height, 0:img_width]
# 保存带后缀的最终裁剪图像
output_image_path = output_path / f"{base_name}_1-9-cropped.jpg"
output_image = Image.fromarray(cv2.cvtColor(cropped_image, cv2.COLOR_BGR2RGB))
output_image.save(output_image_path)
print(f"已处理并保存 {image_path.name}")
def process_all_images_in_folder():
# 设置输入和输出目录
input_dir = Path(input_folder)
output_dir = Path(output_folder)
output_dir.mkdir(exist_ok=True)
# 处理输入目录中的每个图像文件
for image_path in input_dir.glob("*.*"):
if image_path.is_file() and image_path.suffix.lower() in {'.jpg', '.jpeg', '.png', '.webp'}:
try:
crop_important_region(image_path, output_dir)
except Exception as e:
print(f"处理 {image_path.name} 时出错:{e}")
# 在输入文件夹中运行脚本
process_all_images_in_folder()
完成操作。