Гид по технологиям

Стабилизация видео с помощью OpenCV: пошаговый пример на Python

6 min read Компьютерное зрение Обновлено 08 Jan 2026
Стабилизация видео с OpenCV на Python
Стабилизация видео с OpenCV на Python

Важно: пример рассчитан на видеопотоки без дополнительной информации с датчиков. Для съёмки с IMU/гимбалом нужны другие подходы.

Зачем нужна стабилизация видео

Стабилизация уменьшает нежелательное движение и дрожание в видеороликах. При съёмке с рук, вибрации или резком перемещении камеры кадры получаются неустойчивыми. Цель стабилизации — оценить реальное движение камеры между соседними кадрами и применить преобразования, которые выровняют кадры и минимизируют заметное смещение для зрителя.

Краткое определение: стабилизация видео — процесс оценки движения камеры и компенсации этого движения с помощью геометрических преобразований кадров.

Основные идеи алгоритма (в одной строке)

  1. Найти соответствия точек между соседними кадрами, 2) оценить трансформации (перенос, поворот), 3) накопить траекторию, 4) сгладить её фильтром скользящего среднего, 5) применить скорректированные трансформации к кадрам.

Настройка окружения

Создайте виртуальное окружение, чтобы зависимости не конфликтовали с системными пакетами. Затем выполните установку библиотек:

pip install opencv-python numpy  

Эта команда установит NumPy (для численных операций) и OpenCV (для обработки изображений и видео).

Полный исходный код можно хранить в репозитории на GitHub и сопровождать инструкцией по запуску.

Импорт библиотек и три ключевые функции

Создайте новый Python-файл и импортируйте библиотеки в начале скрипта:

import numpy as np  
import cv2

Далее определите три функции, которые понадобятся для стабильной работы алгоритма.

Функция calculate_moving_average

Эта функция вычисляет скользящее среднее по кривой с заданным радиусом — стандартный способ сгладить шум в траектории.

def calculate_moving_average(curve, radius):  
    # Calculate the moving average of a curve using a given radius  
    window_size = 2 * radius + 1  
    kernel = np.ones(window_size) / window_size  
    curve_padded = np.lib.pad(curve, (radius, radius), 'edge')  
    smoothed_curve = np.convolve(curve_padded, kernel, mode='same')  
    smoothed_curve = smoothed_curve[radius:-radius]  
    return smoothed_curve  

Функция возвращает сглаженную кривую и помогает уменьшить дребезг и резкие скачки в траектории.

Функция smooth_trajectory

Применяет скользящее среднее к каждой размерности траектории (смещение по X, по Y, угол поворота).

def smooth_trajectory(trajectory):  
    # Smooth the trajectory using moving average on each dimension  
    smoothed_trajectory = np.copy(trajectory)  
  
    for i in range(3):  
        smoothed_trajectory[:, i] = calculate_moving_average(  
            trajectory[:, i],  
            radius=SMOOTHING_RADIUS  
        )  
  
    return smoothed_trajectory  

Функция возвращает сглаженную копию исходной траектории.

Функция fix_border

Используется для устранения видимых артефактов по краям кадра после аффинных преобразований — применяет лёгкое масштабирование по центру.

def fix_border(frame):  
    # Fix the frame border by applying rotation and scaling transformation  
    frame_shape = frame.shape  
     
    matrix = cv2.getRotationMatrix2D(  
        (frame_shape[1] / 2, frame_shape[0] / 2),  
        0,  
        1.04  
    )  
  
    frame = cv2.warpAffine(frame, matrix, (frame_shape[1], frame_shape[0]))  
    return frame

Эта простая операция масштабирования скрывает чёрные области, которые появляются после сдвига/поворота кадра.

Инициализация и чтение входного видео

Задайте радиус для сглаживания:

SMOOTHING_RADIUS = 50  

Откройте видео с диска или используйте камеру (замените путь на 0):

# Open the input video file  
# Replace the path with 0 to use your webcam  
cap = cv2.VideoCapture('inputvid.mp4')

Получите свойства видео:

num_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))  
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  
fps = cap.get(cv2.CAP_PROP_FPS)

Установите кодек и инициализируйте VideoWriter (обратите внимание на соответствие расширения и кодека):

fourcc = cv2.VideoWriter_fourcc(*'mp4v')  

out = cv2.VideoWriter('video_out.mp4', fourcc, fps, (2 * width, height))

Если хотите сохранять только стабилизированное видео (без колонки «до/после»), поменяйте размер выходного кадра на (width, height).

Чтение кадров и оценка трансформаций

Первый проход по видео — вычисление трансформаций между парой соседних кадров и заполнение массива transforms.

_, prev_frame = cap.read()  
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)  

transforms = np.zeros((num_frames - 1, 3), np.float32)  

for i in range(num_frames - 2):  
    # Calculate optical flow between consecutive frames  
    prev_points = cv2.goodFeaturesToTrack(  
        prev_gray,  
        maxCorners=200,  
        qualityLevel=0.01,  
        minDistance=30,  
        blockSize=3  
    )  
  
    success, curr_frame = cap.read()  
  
    if not success:  
        break  
  
    curr_gray = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)  
  
    curr_points, status, err = cv2.calcOpticalFlowPyrLK(  
        prev_gray,  
        curr_gray,  
        prev_points,  
        None  
    )  
  
    assert prev_points.shape == curr_points.shape  
    idx = np.where(status == 1)[0]  
    prev_points = prev_points[idx]  
    curr_points = curr_points[idx]  
  
    # Estimate affine transformation between the points  
    matrix, _ = cv2.estimateAffine2D(prev_points, curr_points)  
    translation_x = matrix[0, 2]  
    translation_y = matrix[1, 2]  
    rotation_angle = np.arctan2(matrix[1, 0], matrix[0, 0])  
    transforms[i] = [translation_x, translation_y, rotation_angle]  
    prev_gray = curr_gray

Ключевые моменты:

  • cv2.goodFeaturesToTrack находит сильные опорные точки; уменьшение maxCorners уменьшит вычисления.
  • cv2.calcOpticalFlowPyrLK отслеживает найденные точки в следующем кадре.
  • estimateAffine2D оценивает аффинную матрицу между соответствующими точками.

Сглаживание траектории

После сбора трансформаций мы накапливаем их в траекторию и сглаживаем:

# Calculate the trajectory by cumulatively summing the transformations  
trajectory = np.cumsum(transforms, axis=0)  
  
# Smooth the trajectory using moving average  
smoothed_trajectory = smooth_trajectory(trajectory)  
  
# Calculate the difference between the smoothed and original trajectory  
difference = smoothed_trajectory - trajectory  
  
# Add the difference back to the original transformations to obtain smooth  
# transformations  
transforms_smooth = transforms + difference

Сглаживание устраняет быстрые флуктуации, оставляя медленные изменения (плавный пан/наклон).

Применение трансформаций и запись результата

Сбросьте позицию чтения и пройдите по кадрам ещё раз, применяя скорректированные трансформации.

cap.set(cv2.CAP_PROP_POS_FRAMES, 0)  

# Process each frame and stabilize the video  
for i in range(num_frames - 2):  
    success, frame = cap.read()  
  
    if not success:  
        break  
  
    translation_x = transforms_smooth[i, 0]  
    translation_y = transforms_smooth[i, 1]  
    rotation_angle = transforms_smooth[i, 2]  
  
    # Create the transformation matrix for stabilization  
    transformation_matrix = np.zeros((2, 3), np.float32)  
    transformation_matrix[0, 0] = np.cos(rotation_angle)  
    transformation_matrix[0, 1] = -np.sin(rotation_angle)  
    transformation_matrix[1, 0] = np.sin(rotation_angle)  
    transformation_matrix[1, 1] = np.cos(rotation_angle)  
    transformation_matrix[0, 2] = translation_x  
    transformation_matrix[1, 2] = translation_y  
  
    # Apply the transformation to stabilize the frame  
    frame_stabilized = cv2.warpAffine(  
        frame,  
        transformation_matrix,  
        (width, height)  
    )  
  
    # Fix the border of the stabilized frame  
    frame_stabilized = fix_border(frame_stabilized)  
  
    # Concatenate the original and stabilized frames side by side  
    frame_out = cv2.hconcat([frame, frame_stabilized])  
  
    # Resize the frame if its width exceeds 1920 pixels  
    if frame_out.shape[1] > 1920:  
        frame_out = cv2.resize(  
            frame_out,  
            (frame_out.shape[1] // 2, frame_out.shape[0] // 2)  
        )  
  
    # Display the before and after frames  
    cv2.imshow("Before and After", frame_out)  
    cv2.waitKey(10)  
  
    # Write the frame to the output video file  
    out.write(frame_out)

В конце освободите ресурсы:

# Release the video capture and writer, and close any open windows  
cap.release()  
out.release()  
cv2.destroyAllWindows()

Пример выходного изображения

Сравнение исходного и стабилизированного кадра в IDE

На выходе вы получите видео, где слева — исходный, справа — стабилизированный кадр.

Когда этот метод чувствует себя плохо — ограничения и крайние случаи

  • Очень сильная потеря текстуры или размытие движения: детекторы точек не найдут устойчивых сопоставлений.
  • Быстрые резкие тряски и смены кадра (например, прыжок камеры): сглаживание не успеет, и результат будет прыгать.
  • Объекты крупным планом двигаются независимо от камеры: локальные движения могут быть ошибочно интерпретированы как движение камеры.
  • Панорамная съёмка с очень большим зумом: аффинная модель может не описывать параллакс и перспективные искажения.

Важно учитывать, что обработка основана на 2D-результатах: для сложной трёхмерной стабилизации требуются дополнительные модели или данные IMU.

Альтернативные подходы и инструменты

  • Использовать библиотеки высокого уровня: vidstab (Python), которые реализуют похожие пайплайны и удобные API.
  • Применять глобальные методы на основе гомографий и RANSAC — полезно при плоских сценах.
  • Комбинировать визуальные данные с IMU/гироскопом — даёт лучшую точность для мобильных устройств и дронов.
  • Использовать коммерческие решения или встроенные механизмы в видеоредакторах (DaVinci Resolve, Adobe Premiere).

Мини‑методология: шаги для воспроизводимой реализации

  1. Подготовка: виртуальное окружение, входное видео, тестовый набор кадров.
  2. Обнаружение точек: goodFeaturesToTrack с проверкой плотности точек.
  3. Отслеживание: calcOpticalFlowPyrLK с фильтрацией по статусу и ошибке.
  4. Оценка трансформаций: estimateAffine2D + контроль на NaN/неуспех.
  5. Накопление и сглаживание: cumsum → moving average.
  6. Применение: warpAffine → fix_border → запись.
  7. Валидация: визуальная оценка «до/после», проверка fps и синхронизации.

Контрольные списки по ролям

Разработчик:

  • Проверить, что cap.open() успешно открыл файл.
  • Добавить обработку ошибок для estimateAffine2D (matrix может быть None).
  • Настроить параметры goodFeaturesToTrack и радиус сглаживания.

Reviewer/QA:

  • Убедиться, что видео не «зависает» и нет рассинхронизации аудио.
  • Проверить граничные кадры и отсутствие резких артефактов.

Оператор/DevOps:

  • Настроить пайплайн обработки партий видео (batch), логировать ошибки.
  • Контролировать загрузку CPU/GPU и временные метрики обработки.

Шпаргалка параметров и пресеты

  • SMOOTHING_RADIUS: 30–100 — более высокое значение даёт более плавную, но более «инерционную» стабилизацию.
  • maxCorners в goodFeaturesToTrack: 100–1000 — меньше для производительности, больше для устойчивости.
  • qualityLevel: 0.01–0.1 — порог качества точек.
  • minDistance: 10–50 — минимальная дистанция между точками, влияет на распределение точек по кадру.

Совет: начать с пресета SMOOTHING_RADIUS=50 и maxCorners=200, затем адаптировать под конкретное видео.

Критерии приёмки

  • Визуальная разница между «до» и «после»: заметное уменьшение дрожания без сильных потерй кадра.
  • Отсутствие черных областей по краям видимых после финальной обработки.
  • Видео сохраняется с тем же количеством кадров и ориентировочной частотой кадров.

Ключевые термины (одной строкой)

  • Optical flow — оценка попарного перемещения пикселей между кадрами.
  • Affine transform — преобразование, включающее перенос, поворот и масштаб по осям.
  • Trajectory smoothing — фильтрация накопленной последовательности трансформаций.

Риски и возможные смягчения

  • Риск: estimateAffine2D вернёт None. Смягчение: проверять матрицу и при ошибке пропускать кадр или использовать предыдущую матрицу.
  • Риск: недостаточно опорных точек. Смягчение: уменьшать minDistance, повышать maxCorners, или использовать детектор ORB/SIFT для жёстких условий.

Короткое резюме

Приведённый пример показывает базовый рабочий конвейер стабилизации видео с помощью OpenCV: детекция и трекинг опорных точек, оценка аффинных трансформаций, сглаживание траектории и применение коррекции к кадрам. Для продвинутых задач рассматривайте комбинирование с IMU, использование более сложных моделей движения или готовых библиотек.

Примечание: экспериментируйте с параметрами, логируйте промежуточные результаты и добавляйте обработку ошибок для робастности в продакшне.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство