1 指定插值方法改善resize函数缩放图片时出现锯齿感和失真问题

最近使用OpenCV对视频数据进行裁剪和缩放到指定的分辨率时,发现如果只是裁剪视频两侧多余的背景,视频的清晰度还是和原视频一样保持不变,但是如果在裁剪之后继续缩放到指定的分辨率,最后的结果数据就会出现比较严重的锯齿感和失真,与原视频的清晰度差别很大。

1.1 从OpenCV中resize的官方文档中学习

在出现以上问题之后,我细细查看了resize函数的官方文档,文档链接:https://docs.opencv.org/4.x/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d

函数原型

C++的函数原型

void cv::resize (   InputArray  src,
    OutputArray     dst,
    Size    dsize,
    double  fx = 0,
    double  fy = 0,
    int     interpolation = INTER_LINEAR
)

Python的函数原型

    cv.resize(  src, dsize[, dst[, fx[, fy[, interpolation]]]]  ) ->    dst

函数参数

  • src:输入图片;
  • dst:输出图片;
  • dszie:输出图片的大小
  • fx:沿水平方向(宽度)的缩放因子,当fx=0时,即为(double)dsize.width/src.cols;
  • fy:沿垂直方向(高度)的缩放因子,当fy=0时,即为(double)dsize.height/src.rows;
  • interpolation:插值方法,默认的方法为INTER_LINEAR(双线性插值)

其中插值方法为resize函数在缩放图片时使用的插值方式,其中可选的插值方法可参考官方文档https://docs.opencv.org/4.x/da/d54/group__imgproc__transform.html#gga5bb5a1fea74ea38e1a5445ca803ff121acf959dca2480cc694ca016b81b442ceb

这里我们简单的例举以下可用的插值方法:

插值方法 说明
INTER_NEAREST Python: cv.INTER_NEAREST 最近邻插值
INTER_LINEAR Python: cv.INTER_LINEAR 双线性插值
INTER_CUBIC Python: cv.INTER_CUBIC 双三次插值
INTER_AREA Python: cv.INTER_AREA 使用像素面积关系重新采样。这可能是图像抽取的首选方法,因为它可以提供无云纹的结果。但当图像被缩放时,它类似于INTER_NEAREST方法。
INTER_LANCZOS4 Python: cv.INTER_LANCZOS4 8x8邻域上的Lanczos插值
INTER_LINEAR_EXACT Python: cv.INTER_LINEAR_EXACT 位精确双线性插值
INTER_NEAREST_EXACT Python: cv.INTER_NEAREST_EXACT 位精确最近邻插值。这将产生与PIL、scikit图像或Matlab中的最近邻方法相同的结果。

resize函数官方文档页面有以下的一句话

OpenCV – 指定插值方法改善resize函数缩放图片时出现锯齿、失真、清晰度降低问题-StubbornHuang Blog

To shrink an image, it will generally look best with INTER_AREA interpolation, whereas to enlarge an image, it will generally look best with INTER_CUBIC (slow) or INTER_LINEAR (faster but still looks OK).

意思是如果我们要缩小图像,使用INTER_AREA插值方法的效果是最好的,而如果放大图片,INTER_CUBIC的效果是最好的但是速度慢,而INTER_LINEAR方法速度快然后效果看起来也可以。

1.2 合理使用resize时的插值方法

从上述官方文档中学到,由于之前在使用resize时没有显式指定插值方法,而是一直使用的默认插值方法INTER_LINEAR,所以不管是缩小还是放大图片效果都不是很好,而为了比较每一种插值方法,我写了一个Python脚本验证每一个插值方法的效果

# -*- coding: utf-8 -*-

import os
import sys
import cv2
import re

def center_crop_video_according_to_width(input_video_path,out_video_path,interpolation,video_out_size='256x256px'):
    # 判断视频文件是否存在
    if not os.path.exists(input_video_path):
        print('输入的视频文件不存在')
        sys.exit(0)

    # 如果输出文件夹不存在则创建
    output_base_path = os.path.dirname(out_video_path)
    if not os.path.exists(output_base_path):
        try:
            os.makedirs(output_base_path)
        except FileExistsError:
            print('所需创建的文件夹已存在')

    if os.path.exists(out_video_path):
        print('裁剪视频输出文件存在,将删除原有路径视频')
        os.remove(out_video_path)

    # 获取原有视频参数
    video_read_capture = cv2.VideoCapture(input_video_path)
    input_video_width = int(video_read_capture.get(cv2.CAP_PROP_FRAME_WIDTH))
    input_video_height = int(video_read_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
    input_video_fps = int(video_read_capture.get(cv2.CAP_PROP_FPS))
    input_video_fourcc = int(video_read_capture.get(cv2.CAP_PROP_FOURCC))

    # 创建写视频对象
    out_size = tuple(int(res) for res in re.findall("\d+", video_out_size))
    output_video_fourcc = int(cv2.VideoWriter_fourcc(*'mp4v'))
    video_write_capture = cv2.VideoWriter(out_video_path,output_video_fourcc,input_video_fps,out_size)

    while video_read_capture.isOpened():
        result, frame = video_read_capture.read()
        if not result:
            break

        # 裁剪到与原视频高度等宽的视频
        diff = input_video_width - input_video_height
        diff = int(diff / 2)
        crop_start_index = int(diff)
        crop_end_index = int(diff + input_video_height)

        # 参数1 是高度的范围,参数2是宽度的范围
        target = frame[0:int(input_video_height), crop_start_index:crop_end_index]

        # 再resize到目标大小
        target = cv2.resize(target, out_size, interpolation=interpolation)

        # 写输出视频帧
        video_write_capture.write(target)

    video_read_capture.release()
    video_write_capture.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    center_crop_video_according_to_width(r"C:\Users\Administrator\Desktop\video_input\a.mp4",
                                         r"C:\Users\Administrator\Desktop\video_output\a_INTER_NEAREST.mp4",
                                         cv2.INTER_NEAREST)

    center_crop_video_according_to_width(r"C:\Users\Administrator\Desktop\video_input\a.mp4",
                                         r"C:\Users\Administrator\Desktop\video_output\a_INTER_LINEAR.mp4",
                                         cv2.INTER_LINEAR)

    center_crop_video_according_to_width(r"C:\Users\Administrator\Desktop\video_input\a.mp4",
                                         r"C:\Users\Administrator\Desktop\video_output\a_INTER_CUBIC.mp4",
                                         cv2.INTER_CUBIC)

    center_crop_video_according_to_width(r"C:\Users\Administrator\Desktop\video_input\a.mp4",
                                         r"C:\Users\Administrator\Desktop\video_output\a_INTER_AREA.mp4",
                                         cv2.INTER_AREA)

    center_crop_video_according_to_width(r"C:\Users\Administrator\Desktop\video_input\a.mp4",
                                         r"C:\Users\Administrator\Desktop\video_output\a_INTER_LANCZOS4.mp4",
                                         cv2.INTER_LANCZOS4)

    center_crop_video_according_to_width(r"C:\Users\Administrator\Desktop\video_input\a.mp4",
                                         r"C:\Users\Administrator\Desktop\video_output\a_INTER_LINEAR_EXACT.mp4",
                                         cv2.INTER_LINEAR_EXACT)

    center_crop_video_according_to_width(r"C:\Users\Administrator\Desktop\video_input\a.mp4",
                                         r"C:\Users\Administrator\Desktop\video_output\a_INTER_NEAREST_EXACT.mp4",
                                         cv2.INTER_NEAREST_EXACT)

经过比较,在缩小视频时使用INTER_AREA的插值方法生成的图片质量确实是最好的。