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)
相关推荐
云青黛3 小时前
肘部法找k
人工智能·算法·机器学习·聚类
IT_陈寒3 小时前
Java性能调优:从GC日志分析到实战优化的5个关键技巧,让你的应用快如闪电!
前端·人工智能·后端
Hs_QY_FX3 小时前
Python 分类模型评估:从理论到实战(以信用卡欺诈检测为例)
人工智能·python·机器学习·数据挖掘·多分类评估
Aspect of twilight3 小时前
3D Gaussian Splatting论文简要解读与可视化复现(基于gsplat)
人工智能·深度学习·gsplat
deephub3 小时前
REFRAG技术详解:如何通过压缩让RAG处理速度提升30倍
人工智能·python·大语言模型·rag
Dongsheng_20194 小时前
【泛3C篇】AI深度学习在手机背板外观缺陷检测应用方案
图像处理·人工智能·计算机视觉·视觉检测·边缘计算
AI360labs_atyun4 小时前
AI教育开启新篇章
人工智能·百度·ai
成为深度学习高手4 小时前
DGCN+informer分类预测模型
人工智能·分类·数据挖掘
minhuan4 小时前
构建AI智能体:六十六、智能的边界:通过偏差-方差理论理解大模型的能力与局限
人工智能·方差·偏差·方差-偏差分解·方差-偏差权衡·模型调优