You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:使用OpenCV Python实现视频平滑缩放时出现抖动问题

问题描述

我想用OpenCV给视频添加缩放效果,因为OpenCV没有内置缩放功能,所以尝试通过插值计算裁剪的宽高和坐标,裁剪帧后再resize回原视频分辨率1920×1080。但最终渲染的视频出现抖动,无法实现特定时间段内的平滑缩放。

现有代码
import cv2

video_path = 'inputTest.mp4'
cap = cv2.VideoCapture(video_path)

fps = int(cap.get(cv2.CAP_PROP_FPS)) 
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('output_video.mp4', fourcc, fps, (1920, 1080))

initialZoomValue={
     'initialZoomWidth': 1920,
    'initialZoomHeight': 1080,
    'initialZoomX': 0,
    'initialZoomY': 0
}

desiredValues = {
     'zoomWidth': 1672,
     'zoomHeight': 941,
     'zoomX': 200,
     'zoomY': 0
}

def ease_out_quart(t):
    return 1 - (1 - t) ** 4

async def zoomInInterpolation(initialZoomValue, desiredZoom, start, end, index):
    t = (index - start) / (end - start)
    eased_t = ease_out_quart(t)

    interpolatedWidth = round(initialZoomValue['initialZoomWidth'] + eased_t * (desiredZoom['zoomWidth']['width'] - initialZoomValue['initialZoomWidth']), 2)
    interpolatedHeight = round(initialZoomValue['initialZoomHeight'] + eased_t * (desiredZoom['zoomHeight'] - initialZoomValue['initialZoomHeight']), 2)
    interpolatedX = round(initialZoomValue['initialZoomX'] + eased_t * (desiredZoom['zoomX'] - initialZoomValue['initialZoomX']), 2)
    interpolatedY = round(initialZoomValue['initialZoomY'] + eased_t * (desiredZoom['zoomY'] - initialZoomValue['initialZoomY']), 2)
    
    return {'interpolatedWidth': int(interpolatedWidth), 'interpolatedHeight': int(interpolatedHeight), 'interpolatedX': int(interpolatedX), 'interpolatedY': int(interpolatedY)}

def generate_frame():
        while cap.isOpened():
            code, frame = cap.read()
            if code:
                yield frame
            else:
                print("bailsdfing")
                break

for i, frame in enumerate(generate_frame()):
   if i >= 1 and i <= 60:
        interpolatedValues = zoomInInterpolation(initialZoomValue, desiredValues, 1, 60, i)
        crop = frame[interpolatedValues['interpolatedY']:(interpolatedValues['interpolatedHeight'] + interpolatedValues['interpolatedY']), interpolatedValues['interpolatedX']:(interpolatedValues['interpolatedWidth'] + interpolatedValues['interpolatedX'])]
        zoomedFrame = cv2.resize(crop,(1920, 1080), interpolation = cv2.INTER_CUBIC) 

        out.write(zoomedFrame)

# Release the video capture and close windows
cap.release()
cv2.destroyAllWindows()
问题现象

最终生成的视频存在明显抖动。

插值数值说明
  • 插值数值的平滑曲线图表:显示参数随帧索引的连续变化趋势
  • 取整后的插值数值图表:显示参数变为整数后出现的阶梯状突变
限制条件

OpenCV的裁剪操作仅接受整数坐标和尺寸,无法使用插值得到的小数数值。

解决方案

抖动的核心原因是提前对插值参数取整,导致每帧裁剪区域出现阶梯式跳变,而非连续平滑过渡。改用浮点精度的仿射变换替代“裁剪+resize”流程,可彻底规避整数限制带来的抖动问题。

修改后的代码示例:

import cv2
import numpy as np

video_path = 'inputTest.mp4'
cap = cv2.VideoCapture(video_path)

fps = int(cap.get(cv2.CAP_PROP_FPS)) 
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('output_video.mp4', fourcc, fps, (1920, 1080))

# 初始状态:无缩放,偏移为0
initial_scale = 1.0
initial_offset_x = 0.0
initial_offset_y = 0.0

# 目标状态:计算缩放系数(原尺寸/目标裁剪尺寸)
target_width = 1672
target_height = 941
target_scale_x = 1920 / target_width
target_scale_y = 1080 / target_height
# 目标偏移:将裁剪区域的x=200对应到输出帧的x=0,偏移量为 -200 * 缩放系数
target_offset_x = -200 * target_scale_x
target_offset_y = 0.0

def ease_out_quart(t):
    return 1 - (1 - t) ** 4

def zoomInInterpolation(start_frame, end_frame, current_frame):
    t = (current_frame - start_frame) / (end_frame - start_frame)
    eased_t = ease_out_quart(t)
    
    # 插值计算缩放系数和偏移量(保留浮点精度)
    scale_x = initial_scale + eased_t * (target_scale_x - initial_scale)
    scale_y = initial_scale + eased_t * (target_scale_y - initial_scale)
    offset_x = initial_offset_x + eased_t * (target_offset_x - initial_offset_x)
    offset_y = initial_offset_y + eased_t * (target_offset_y - initial_offset_y)
    
    return scale_x, scale_y, offset_x, offset_y

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    
    frame_idx = int(cap.get(cv2.CAP_PROP_POS_FRAMES)) - 1  # 从0开始计数
    
    if 1 <= frame_idx <= 60:
        scale_x, scale_y, offset_x, offset_y = zoomInInterpolation(1, 60, frame_idx)
        
        # 构建仿射变换矩阵:先缩放,再平移
        M = np.float32([[scale_x, 0, offset_x], [0, scale_y, offset_y]])
        
        # 应用仿射变换,保持输出尺寸为1920x1080,使用双三次插值
        zoomed_frame = cv2.warpAffine(frame, M, (1920, 1080), flags=cv2.INTER_CUBIC)
        out.write(zoomed_frame)
    else:
        # 非缩放时间段直接输出原帧
        out.write(frame)

cap.release()
out.release()
cv2.destroyAllWindows()

关键改进点

  • 用仿射变换替代“裁剪+resize”流程,浮点精度的变换参数保证每帧过渡连续,彻底消除抖动
  • 保留缓动函数逻辑,确保缩放动画曲线符合预期
  • 直接对整帧进行变换,规避裁剪操作的整数限制问题

内容的提问来源于stack exchange,提问作者Zaid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 16:15:54