
系列:《图像处理算法入门 144 期》· 模块二:数字图像基础操作
标签:
OpenCV/Pillow/resize/ROI/插值/Letterbox/入门
一、前言
上一期(011)我们聊了图像的"身份证"------shape / dtype / size,搞清楚了图像在内存里到底是什么样子。本期进入 M02 基础操作的第四期 ,主题聚焦两个最高频的需求:缩放(resize) 与 裁剪(ROI)。
无论是给深度学习模型准备输入数据、生成缩略图、还是做图像增强预处理,cv2.resize 都是绕不开的 API。但你真的用对了吗?
| 场景 | 推荐插值 | 理由 |
|---|---|---|
| 模型推理输入预处理 | INTER_LINEAR / INTER_AREA(缩小) |
速度快,效果稳;YOLO/ResNet 默认 |
| 高质量图像放大 | INTER_CUBIC / INTER_LANCZOS4 |
边缘更平滑,保留细节 |
| 实时视频缩放 | INTER_NEAREST |
极限速度优先 |
| 数据增强随机裁剪 | 任意,配合 ROI 切片 | 内容不变,关注几何变换 |
本期会带你:
- 用 4 种插值 做 resize 对比
- 用 PSNR + SSIM 客观评测差异
- 学会两种 ROI 裁剪 写法(OpenCV 切片 vs Pillow crop)
- 封装一个生产级的 智能缩放工具(Letterbox)
二、resize 的四种插值方法
2.1 插值原理速览
缩放的本质是用原图若干像素去"估"出新图的像素值。插值方法决定了估计的精度与速度:
| 插值方法 | OpenCV 常量 | 复杂度 | 边缘特征 | 典型用途 |
|---|---|---|---|---|
| 最近邻 | INTER_NEAREST |
O(1) | 锯齿明显 | 实时视频、像素画 |
| 双线性 | INTER_LINEAR |
O(4) | 稍模糊 | 通用(OpenCV 默认) |
| 像素区域(缩小专用) | INTER_AREA |
- | 最佳 | 缩小操作 |
| 双三次 | INTER_CUBIC |
O(16) | 平滑锐利 | 高质量放大 |
| 兰索斯 4×4 | INTER_LANCZOS4 |
O(64) | 综合最优 | 专业级放大 |
2.2 一行代码对比
python
import cv2
img = cv2.imread("photo.jpg") # 原始 HxW
methods = {
"最近邻": cv2.INTER_NEAREST,
"双线性": cv2.INTER_LINEAR,
"双三次": cv2.INTER_CUBIC,
"兰索斯": cv2.INTER_LANCZOS4,
}
for name, flag in methods.items():
resized = cv2.resize(img, (new_w, new_h), interpolation=flag)
OpenCV 的经验法则:
- 缩小 →
cv2.INTER_AREA(避免摩尔纹) - 放大 →
cv2.INTER_CUBIC/cv2.INTER_LANCZOS4 - 通用 →
cv2.INTER_LINEAR
三、客观质量评估:PSNR 与 SSIM
光看主观图不够,我们用 PSNR(峰值信噪比) 和 SSIM(结构相似性) 量化对比:
- PSNR 越大越好(单位 dB,>30dB 通常视为难以察觉损失)
- SSIM ∈ 0, 1,越接近 1 越好
对同一张测试图(540×360→216×144→还原到 540×360)做 4 种插值,实测得分如下:
| 插值方法 | PSNR (dB) | SSIM |
|---|---|---|
| INTER_NEAREST (最近邻) | 16.41 | 0.9737 |
| INTER_LINEAR (双线性) | 20.10 | 0.9886 |
| INTER_CUBIC (双三次) | 20.08 | 0.9884 |
| INTER_LANCZOS4 (兰索斯) | 20.04 | 0.9881 |

结论 :在这张测试图上双线性反而是综合最优------双三次与兰索斯因轻微振铃(ringing)拉低了得分。最近邻分数明显偏低,锯齿丢失了边缘结构信息。
这告诉我们:没有"最好的插值",只有"最适合场景的插值"。对于自然风景照、人像,兰索斯更优;对于 UI 截图、几何线条,最近邻更合适。
四、ROI 裁剪:OpenCV vs Pillow 两种写法
ROI(Region of Interest,感兴趣区域)是图像处理的"基本手"------裁框、人脸定位、目标检测标注都靠它。
4.1 OpenCV 写法(NumPy 切片)
OpenCV 图像就是 np.ndarray,最直接的裁法就是切片:
python
img = cv2.imread("photo.jpg") # shape: (H, W, C) ← 注意顺序!
roi = img[y1:y2, x1:x2] # 行(高)维度先,列(宽)维度后
坑点 :img.shape 是 (H, W, C),但是 cv2.resize(img, (W, H)) 的参数是 (W, H),很多初学者都会搞反!
4.2 Pillow 写法(Image.crop)
Pillow 把图像当对象,坐标更"自然":
python
from PIL import Image
pil_img = Image.open("photo.jpg") # size: (W, H) ← 也是 W 先!
roi = pil_img.crop((left, upper, right, lower)) # box 是 (x1, y1, x2, y2)
两种写法结果完全一致,只是风格差异:

核心记忆点:
- OpenCV / NumPy → 先高度
H(行),后宽度W(列)- Pillow /
cv2.resize→ 先宽度W(列),后高度H(行)
五、生产级工具:智能缩放(Letterbox)
5.1 为什么需要 Letterbox?
直接 cv2.resize 会强制拉伸------把一张竖向人像压缩成正方形,脸会变形,这在给深度学习模型准备输入时是致命的:
原始 540×360 → 直接 resize 到 224×224 → 拉伸变形 ❌
原始 540×360 → Letterbox 到 224×224 → 等比缩放 + 灰边 ✅
Letterbox(letterboxing,"信箱模式")的策略:
- 计算缩放比例 =
min(target_w/src_w, target_h/src_h),取较小者保证完整放入 - 按比例缩放 → 居中放置 → 四周填中性灰
(114, 114, 114)(YOLO 默认值)
5.2 完整实现
python
def smart_resize_keep_ratio(
img, target_w, target_h, pad_color=(114, 114, 114)
):
h, w = img.shape[:2]
scale = min(target_w / w, target_h / h)
new_w, new_h = int(w * scale), int(h * scale)
# 1) 等比缩放
resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
# 2) 创建目标画布并居中放置
canvas = np.full((target_h, target_w, 3), pad_color, dtype=np.uint8)
x_off = (target_w - new_w) // 2
y_off = (target_h - new_h) // 2
canvas[y_off:y_off + new_h, x_off:x_off + new_w] = resized
return canvas, scale, (x_off, y_off, new_w, new_h)
5.3 四种缩放策略对比

什么时候用哪种策略?
场景 推荐策略 备注 目标检测模型输入 Letterbox YOLOv5/v8 默认 图像分类模型输入 Letterbox 或中心裁剪 ResNet/VGG 网页缩略图 Letterbox 不变形即可 头像/封面裁剪 中心裁剪 抛弃背景信息 打印/印刷品 直接拉伸到目标尺寸 高分辨率源图
5.4 一键输出深度学习输入尺寸
下面这行代码就能把任意尺寸图缩到 224×224(ResNet 经典输入):
python
lb_224, scale, box = smart_resize_keep_ratio(img, 224, 224)
输出效果(540×360→224×224,scale=0.4148,等比缩放居中):

六、完整代码
完整脚本已保存到 code/012_resize_crop.py,运行后会自动在 code/output/ 下生成 5 张图:
code/012_resize_crop.py # 本期代码
code/output/012_source.png # 测试原图
code/output/012_interpolation_quality.png # PSNR+SSIM 对比
code/output/012_roi_crop_compare.png # OpenCV vs Pillow ROI
code/output/012_smart_resize_compare.png # 4 种缩放策略
code/output/012_letterbox_224.png # 深度学习输入样例
运行方式:
bash
"C:/Users/kazen/.workbuddy/binaries/python/envs/cv_env/Scripts/python.exe" code/012_resize_crop.py
七、常见问题 FAQ
Q1:缩放后颜色变了(偏色),怎么办?
检查 dtype:若图像是
float32,插值可能产生负值或 >1 的值,务必np.clip(..., 0, 255)后再转回uint8。
Q2:Letterbox 的填充色为什么是 (114,114,114)?
这是 YOLOv5/v8 的默认值,对应 0.45 灰度。对于亮度自适应模型(如 SAM),可以改为 (0,0,0),或用图像均值作为填充色。
Q3:放大到很大的尺寸(10 倍+)出现明显模糊?
这是插值的"硬天花板"。需要上深度学习方案:Real-ESRGAN / Topaz Gigapixel AI / SD 放大。详细对比将在 M13 深度学习模块展开。
Q4:批量缩放一堆图,怎么写最简洁?
用
pathlib.Path.glob('*.jpg')+tqdm加进度条,或者用Image.open+PIL.ImageOps.fit()(带自动裁剪)。具体代码我们会在 017 期「批量处理 pipeline」详细讲。
Q5:Pillow 的 thumbnail() 和 resize() 有什么区别?
thumbnail()是原地操作 ,只缩小不放大;resize()返回新图,能双向缩放。
八、课后练习
- 基础练习 :用 OpenCV 写一个
pad_to_square(img, fill_color),把任意矩形图补成正方形。 - 进阶练习 :扩展
smart_resize_keep_ratio,支持"右对齐 / 左对齐 / 底部对齐"等非居中模式(YOLOv5-Aug 增强版本)。 - 挑战练习 :实现一个
resize_and_cover(类似 CSSobject-fit: cover),先等比缩放到完全覆盖目标框,再中心裁剪,对比 letterbox 的视觉效果差异。
九、本期小结 & 下期预告
| 维度 | 内容 |
|---|---|
| 核心 API | cv2.resize()、cv2.INTER_*、PIL.Image.crop() |
| 核心策略 | Letterbox(保比例+灰边)、中心裁剪(切边) |
| 评估指标 | PSNR、SSIM |
| 实战工具 | smart_resize_keep_ratio 智能缩放 |
| 下一步 | 013 期:图像旋转与翻转:几何重采样详解,讲任意角度旋转 + 自适应画布 |
本文为《图像处理算法入门 144 期》系列第 012 期,面向入门者强调实操性,所有代码均经过本地实测运行。如有疏漏,欢迎评论区指正。
配套仓库:https://github.com/yourname/image-processing-144(示例代码,无实际依赖)
推荐产品(本期主题:缩放与裁剪)
| 类别 | 代表产品 | 用例 |
|---|---|---|
| 移动端 | 稿定设计(图片裁剪/拼图)、剪映(视频帧裁剪)、醒图(智能裁切主体) | 日常裁图、构图优化 |
| 专业级 | Figma(设计稿自适应框)、Adobe Photoshop(裁剪工具+内容识别缩放)、Capture One(高保真批量缩放) | 设计交付、商业出片 |