PIL与OpenCV双线性插值实现差异导致模型精度不够踩坑

起因

训练分类模型使用的是PIL库的resize,指定biliner算法采样,部署使用的是c++ opencv,发现opencv resize后的和PIL resize的不一样,cv的resize图片噪点多,而PIL的很平滑反而和cv的INTER_AREA插值效果差不多,PIL的库很可能做了均值采样。

在图像处理中,双线性插值(Bilinear Interpolation)是最常用的图像缩放算法之一。然而,不同图像处理库的具体实现可能存在差异。本文通过实验对比Python中PIL(Pillow)库和OpenCV库的双线性插值实现差异。

双线性插值原理回顾

双线性插值是一种在二维空间进行线性插值的方法,它通过四个最近的像素点计算新像素值。基本步骤包括:

  1. 计算目标像素在原图像中的对应位置(通常是非整数坐标)
  2. 确定该位置周围的四个最近邻像素
  3. 分别在水平和垂直方向进行线性插值
  4. 组合两次插值结果得到最终像素值

实验设计与实现

我设计了一个对比实验来验证PIL和OpenCV在双线性插值实现上的差异:

python 复制代码
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
from PIL import Image
from PIL.Image import Resampling

def compare_images(img1, img2):
    """比较两幅图像的相似度"""
    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
    
    # 计算结构相似性指数
    ssim_score, _ = ssim(gray1, gray2, full=True)
    
    # 计算平均像素差异
    diff = cv2.absdiff(img1, img2)
    diff_pixels = np.sum(diff) / (img1.shape[0] * img1.shape[1] * 3)
    
    # 计算欧氏距离
    vec_dist = np.linalg.norm(img1.astype(float) - img2.astype(float))
    
    return ssim_score, diff_pixels, vec_dist

实验方法

对比两种缩放路径:

  1. 直接缩放:从原图直接缩放到目标尺寸(50×50)
  2. 二次缩放:先放大到中间尺寸(57×57),再缩小到目标尺寸(50×50)

PIL库实现

python 复制代码
img = Image.open('input.jpg')
resized_direct_pil = img.resize((50, 50), Resampling.BILINEAR)
resized_up_down_pil = img.resize((100, 100), Resampling.BILINEAR).resize((50, 50), Resampling.BILINEAR)

OpenCV实现

python 复制代码
original_img = cv2.imread('input.jpg')
resized_direct = cv2.resize(original_img, (50, 50), interpolation=cv2.INTER_LINEAR)
resized_up_down = cv2.resize(original_img, (57, 57), interpolation=cv2.INTER_LINEAR)
resized_up_down = cv2.resize(resized_up_down, (50, 50), interpolation=cv2.INTER_LINEAR)

实验结果对比

PIL库结果

复制代码
结构相似性指数(SSIM): 0.9986
平均像素差异: 0.70
向量欧氏距离: 101.26

OpenCV结果

复制代码
结构相似性指数(SSIM): 0.9051
平均像素差异: 4.65
向量欧氏距离: 558.88

差异分析与讨论

  1. PIL的实现特性

    • 不同缩放路径下结果高度一致(SSIM 0.9986)
    • 对缩放路径不敏感,表现出更好的稳定性
    • 可能采用了更精确的坐标映射和权重计算
  2. OpenCV的实现特性

    • 不同缩放路径下差异较明显(SSIM 0.9051)
    • 对中间缩放尺寸敏感
    • 可能在边界处理和插值计算上有不同策略
  3. 可能的原因

    • 坐标映射方式不同(PIL可能使用中心对齐,OpenCV可能使用角点对齐)
    • 边界像素处理策略不同
    • 浮点数计算精度差异
    • 颜色通道处理顺序差异(PIL使用RGB,OpenCV使用BGR)

实际应用建议

  1. 一致性要求高的场景

    • 建议在整个流程中使用同一图像处理库
    • 避免混合使用PIL和OpenCV的缩放操作
  2. 性能考虑

    • OpenCV通常在大图像处理上性能更优
    • PIL在小图像处理上可能更精确
  3. 版本控制

    • 不同版本的库可能有不同的实现细节
    • 建议记录使用的库版本号
  4. 预处理建议

    • 对于关键应用,建议进行充分的预处理测试
    • 可以考虑添加对齐处理或后处理来减小差异

完整代码

复制代码
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
from PIL import Image
from PIL.Image import Resampling  # 新版本推荐方式
def compare_images(img1, img2):
    # 转换为灰度图像以计算SSIM
    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
    
    # 计算结构相似性指数(SSIM)
    ssim_score, _ = ssim(gray1, gray2, full=True)
    
    # 计算像素差异
    diff = cv2.absdiff(img1, img2)
    diff_pixels = np.sum(diff) / (img1.shape[0] * img1.shape[1] * 3)  # 平均每个通道的像素差异
    
    # 计算欧氏距离(向量相似度)
    vec_dist = np.linalg.norm(img1.astype(float) - img2.astype(float))
    
    return ssim_score, diff_pixels, vec_dist



def compare_images_pil(pil_img1, pil_img2):
    # 将PIL图像转换为numpy数组
    img1_np = np.array(pil_img1)
    img2_np = np.array(pil_img2)
    
    # 检查图像维度
    if len(img1_np.shape) == 2:  # 灰度图像
        # 直接使用灰度图像
        gray1 = img1_np
        # 不需要颜色空间转换
        img1_np_bgr = cv2.cvtColor(img1_np, cv2.COLOR_GRAY2BGR) if len(img1_np.shape) == 2 else img1_np
    else:  # 彩色图像
        # PIL图像是RGB格式,OpenCV需要BGR格式
        if img1_np.shape[2] == 4:  # RGBA图像
            img1_np = cv2.cvtColor(img1_np, cv2.COLOR_RGBA2BGR)
        else:  # RGB图像
            img1_np = cv2.cvtColor(img1_np, cv2.COLOR_RGB2BGR)
        gray1 = cv2.cvtColor(img1_np, cv2.COLOR_BGR2GRAY)
    
    if len(img2_np.shape) == 2:  # 灰度图像
        gray2 = img2_np
        img2_np_bgr = cv2.cvtColor(img2_np, cv2.COLOR_GRAY2BGR) if len(img2_np.shape) == 2 else img2_np
    else:  # 彩色图像
        if img2_np.shape[2] == 4:  # RGBA图像
            img2_np = cv2.cvtColor(img2_np, cv2.COLOR_RGBA2BGR)
        else:  # RGB图像
            img2_np = cv2.cvtColor(img2_np, cv2.COLOR_RGB2BGR)
        gray2 = cv2.cvtColor(img2_np, cv2.COLOR_BGR2GRAY)
    
    # 计算结构相似性指数(SSIM)
    ssim_score, _ = ssim(gray1, gray2, full=True)
    
    # 确保两个图像都是3通道(BGR)用于后续计算
    if len(img1_np.shape) == 2:
        img1_np = cv2.cvtColor(img1_np, cv2.COLOR_GRAY2BGR)
    if len(img2_np.shape) == 2:
        img2_np = cv2.cvtColor(img2_np, cv2.COLOR_GRAY2BGR)
    
    # 计算像素差异
    diff = cv2.absdiff(img1_np, img2_np)
    diff_pixels = np.sum(diff) / (img1_np.shape[0] * img1_np.shape[1] * 3)  # 平均每个通道的像素差异
    
    # 计算欧氏距离(向量相似度)
    vec_dist = np.linalg.norm(img1_np.astype(float) - img2_np.astype(float))
    
    return ssim_score, diff_pixels, vec_dist
# 加载原始图像
original_img = cv2.imread(r'F:\sms\traindata\trainwx\empty\825_r90_1.jpg')  # 替换为你的图片路径
if original_img is None:
    print("无法加载图像,请检查路径")
    exit()

# 1. 调整图片到500x500(如果原图不是500x500)

# 2. 方法1
resized_direct = cv2.resize(original_img, (50, 50), interpolation=cv2.INTER_LINEAR)

# 3. 方法2
resized_up_down = cv2.resize(original_img, (57, 57), interpolation=cv2.INTER_LINEAR)
resized_up_down = cv2.resize(resized_up_down, (50, 50), interpolation=cv2.INTER_LINEAR)



#使用python的库再验证
img = Image.open(r'F:\sms\traindata\trainwx\empty\825_r90_1.jpg')
resized_direct_pil = img.resize((50, 50), Resampling.BILINEAR)
resized_up_down_pil = img.resize((100, 100), Resampling.BILINEAR).resize((50, 50), Resampling.BILINEAR)

#对比py的结果
ssim_score, diff_pixels, vec_dist = compare_images_pil(resized_direct_pil, resized_up_down_pil)

print(f"py结构相似性指数(SSIM): {ssim_score:.4f}")
print(f"py平均像素差异: {diff_pixels:.2f}")
print(f"py向量欧氏距离: {vec_dist:.2f}")

# 保存图片
resized_direct_pil.save('resized_direct_pil_250x250.jpg')
resized_up_down_pil.save('resized_up_down_pil_250x250.jpg')


# 4. 比较两种方法的结果(理论上应该完全相同)
ssim_score, diff_pixels, vec_dist = compare_images(resized_direct, resized_up_down)

# 5. 显示结果
print(f"结构相似性指数(SSIM): {ssim_score:.4f}")
print(f"平均像素差异: {diff_pixels:.2f}")
print(f"向量欧氏距离: {vec_dist:.2f}")

# 保存结果图像
cv2.imwrite('resized_direct_250x250.jpg', resized_direct)
cv2.imwrite('resized_up_down_250x250.jpg', resized_up_down)
相关推荐
nancy_princess5 小时前
clip实验
人工智能·深度学习
飞哥数智坊5 小时前
TRAE Friends@济南第4次活动:100+极客集结,2小时极限编程燃爆全场!
人工智能
AI自动化工坊5 小时前
ProofShot实战:给AI编码助手添加可视化验证,提升前端开发效率3倍
人工智能·ai·开源·github
飞哥数智坊5 小时前
一场直播涨粉 2 万的背后!OpenClaw + 飞书,正在重塑软件交付的方式
人工智能
飞哥数智坊5 小时前
养虾记第3期:安装、调教、落地,这场沙龙我们全聊了
人工智能
再不会python就不礼貌了5 小时前
从工具到个人助理——AI Agent的原理、演进与安全风险
人工智能·安全·ai·大模型·transformer·ai编程
AI医影跨模态组学5 小时前
Radiother Oncol 空军军医大学西京医院等团队:基于纵向CT的亚区域放射组学列线图预测食管鳞状细胞癌根治性放化疗后局部无复发生存期
人工智能·深度学习·医学影像·影像组学
A尘埃5 小时前
神经网络的激活函数+损失函数
人工智能·深度学习·神经网络·激活函数
没有不重的名么6 小时前
Pytorch深度学习快速入门教程
人工智能·pytorch·深度学习
有为少年6 小时前
告别“唯语料论”:用合成抽象数据为大模型开智
人工智能·深度学习·神经网络·算法·机器学习·大模型·预训练