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

Стабилизация видео с OpenCV: пошаговое руководство и код

6 min read Компьютерное зрение Обновлено 28 Dec 2025
Стабилизация видео с OpenCV — руководство
Стабилизация видео с OpenCV — руководство

Человек в белой футболке перед компьютером с кодом

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

Основная цель стабилизации — оценить движение камеры между соседними кадрами и применить преобразования, выравнивающие кадры. Это минимизирует воспринимаемое колебание.

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

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

pip install opencv-python numpy

NumPy отвечает за численные операции, OpenCV — за обработку изображений и видео. Исходный код полностью доступен в репозитории на GitHub (ссылка в исходном материале).

Импорт библиотек и определение трёх базовых функций

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

import numpy as np
import cv2

Импорт этих библиотек позволит использовать их функции далее.

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

Функция calculate_moving_average

Эта функция вычисляет скользящее среднее по входной кривой с заданным радиусом окна. Использует свёртку с равномерным ядром.

def calculate_moving_average(curve, 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):
    # Сгладить траекторию, применяя скользящее среднее к каждой компоненте
    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):
    # Исправить границы кадра с помощью небольшой масштабной трансформации
    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 для камеры):

# Открыть входное видео
# Замените путь на 0, чтобы использовать веб-камеру
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)

Укажите формат вывода и инициализируйте видеописатель:

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

Обратите внимание: расширение выходного файла должно соответствовать используемому кодеку/формату.

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

Основная часть — чтение кадров, обнаружение ключевых точек, вычисление оптического потока и оценка аффинного преобразования между кадрами.

_, 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):
    # Найти хорошие точки для отслеживания в предыдущем кадре
    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]

    # Оценить аффинное преобразование между наборами точек
    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.calcOpticalFlowPyrLK) для отслеживания точек. Точки с статусом 1 (успешно отследились) применяются для оценки аффинной матрицы.

Важно: если количество отслеживаемых точек слишком мало, оценка матрицы станет нестабильной. В таких случаях имеет смысл уменьшить minDistance или увеличить maxCorners.

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

После вычисления последовательных трансформаций построим траекторию камеры и сгладим её:

# Построить траекторию суммированием трансформаций
trajectory = np.cumsum(transforms, axis=0)

# Сгладить траекторию
smoothed_trajectory = smooth_trajectory(trajectory)

# Разница между сглаженной и исходной траекторией
difference = smoothed_trajectory - trajectory

# Получить сглаженные трансформации
transforms_smooth = transforms + difference

Таким образом мы корректируем каждую трансформацию так, чтобы общая траектория выглядела плавнее.

Применение стабилизации и запись результата

Сбросьте позицию видеопотока и примените рассчитанные сглаженные преобразования ко всем кадрам:

cap.set(cv2.CAP_PROP_POS_FRAMES, 0)

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]

    # Создать матрицу трансформации
    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

    # Применить трансформацию для стабилизации кадра
    frame_stabilized = cv2.warpAffine(
        frame,
        transformation_matrix,
        (width, height)
    )

    # Исправить границу
    frame_stabilized = fix_border(frame_stabilized)

    # Сравнение: оригинал и стабилизированный кадр бок о бок
    frame_out = cv2.hconcat([frame, frame_stabilized])

    if frame_out.shape[1] > 1920:
        frame_out = cv2.resize(
            frame_out,
            (frame_out.shape[1] // 2, frame_out.shape[0] // 2)
        )

    cv2.imshow("Before and After", frame_out)
    cv2.waitKey(10)

    out.write(frame_out)

Завершение работы

Освободите ресурсы и закройте окна:

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

Результат стабилизации видео в среде PyCharm

Выходной ролик показывает сравнение дрожащего и стабилизированного видео.

Советы по параметрам и оптимизациям

  • SMOOTHING_RADIUS: более высокий радиус даёт более плавную траекторию, но сильнее обрезает края и может «замыливать» быстрые движения. Для съёмки с рук обычно подходят значения 30–60; для экстремальной стабилизации — выше. Выбирайте, исходя из желаемого баланса между гладкостью и потерей кадра.
  • Выбор точек: maxCorners и minDistance влияют на плотность и распределение треков. Для текстурированных сцен можно снизить minDistance, чтобы получить больше точек.
  • Отслеживание: если кадры содержат быстрый поток движения или размытость, cv2.calcOpticalFlowPyrLK может давать много ошибок. Увеличьте pyramid levels или примените препроцессинг (усиление контраста, размытие).
  • Масштабирование: fix_border использует масштаб 1.04; при сильных сдвигах может потребоваться другое значение или контент-aware заполнение краёв.
  • Производительность: для длинных видео обрабатывайте кадры пакетами, сохраняйте промежуточные трансформации и используйте многопоточность для кодирования/декодирования.

Когда этот метод даёт плохие результаты

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

В таких случаях рассмотрите альтернативы (см. ниже) или комбинируйте с детектированием переднего плана/масками.

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

  • estimateAffinePartial2D с RANSAC: устойчивее к выбросам точек, так как отбрасывает неподходящие соответствия.
matrix, inliers = cv2.estimateAffinePartial2D(prev_points, curr_points, method=cv2.RANSAC)
  • Библиотеки высшего уровня: vidstab (Python) предоставляет готовые алгоритмы стабилизации и более сложные стратегии кадрирования.
  • FFmpeg deshake: простая командная утилита для базовой стабилизации без программирования.
  • Глубинные методы: нейросетевые подходы (Deep Video Stabilization) дают отличный результат для сложных сцен, но требуют обучения и ресурсов.

Практические эвристики и правила выбора

  • Если кадр дрожит со средней амплитудой — используйте аффинную модель + скользящее среднее.
  • Для сильных локальных движений добавляйте маскирование движущихся объектов.
  • Если важна каждая деталь в кадре (без кадрирования) — минимизируйте SMOOTHING_RADIUS и используйте аппроксимацию с коррекцией краёв.

Чек-лист для разработчика и тестирования

  • Код успешно открывает входной файл и считывает свойства видео.
  • Количество вычисленных трансформаций равно числу кадров минус 1.
  • Среднее количество треков на кадр достаточно (например, > 50) для стабильной оценки.
  • Итоговое видео проигрывается с той же частотой кадров и без значительных пропусков.
  • Визуальная проверка: дрожание заметно уменьшилось, не появилось чрезмерного кадрирования.

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

  • Стабилизация визуально уменьшила дрожание в большинстве сцен.
  • Нет значительных чёрных полос по краям; если они есть — они должны быть исправлены через масштабирование или заполнение.
  • FPS выходного файла примерно соответствует исходному.
  • Непрерывность сюжета сохранена (без странных скачков в кадрах).

Короткая методология внедрения (SOP)

  1. Проверьте качество входного видео и метаданные (ширина, высота, fps).
  2. Настройте параметры детекции точек (maxCorners, qualityLevel).
  3. Протестируйте вычисление трансформаций на небольшом отрезке (200–500 кадров).
  4. Сгладьте траекторию, оцените визуально и по метрикам (снижение спектра трансформаций).
  5. Обработайте всё видео, проверьте итог и отрегулируйте параметры.

Совместимость и замечания

  • Код основан на стандартных функциях OpenCV, совместим с OpenCV 3.x/4.x. В разных версиях могут отличаться возвращаемые значения у некоторых функций; проверяйте API вашего окружения.
  • Тестируйте код на небольших фрагментах перед обработкой полного ролика, чтобы подобрать параметры.

Быстрые шаблоны и сниппеты

  • Пример использования RANSAC:
matrix, inliers = cv2.estimateAffinePartial2D(prev_points, curr_points, method=cv2.RANSAC, ransacReprojThreshold=3)
  • Быстрая проверка наличия достаточного количества точек:
if prev_points is None or len(prev_points) < 10:
    # изменить параметры обнаружения точек или пропустить кадр
    continue

Частые проблемы и способы устранения

  • Мало точек: увеличить maxCorners, снизить minDistance, улучшить контраст.
  • Появление None из estimateAffine*: использовать RANSAC или пропустить кадр.
  • Чёрные края: увеличить масштаб в fix_border или применить алгоритм заполнения контента.

Заключение

Стабилизация с помощью OpenCV и простого скользящего среднего — надёжный и понятный метод, который даёт хороший результат в большинстве сценариев съёмки с рук. Для сложных сцен стоит комбинировать подходы: использовать RANSAC, маскирование движений или готовые библиотеки/нейросети. Начните с базового алгоритма, подберите параметры на тестовом фрагменте и затем обработайте весь ролик.

Важно: проверяйте выходные видео визуально и по практическим критериям приёмки — автоматические метрики полезны, но человеческое восприятие решающее.

Заметки:

  • Important: тестируйте скорость и нагрузку при обработке длинных роликов — кодирование/декодирование часто становится узким местом.
  • Note: для видеопотоков в реальном времени требуется иная архитектура — буферизация и апроксимации с ограниченным окном кадра.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

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

Установка MS SQL Server на Ubuntu 20.04
Databases

Установка MS SQL Server на Ubuntu 20.04

Экономия трафика при раздаче интернета
Сетевые советы

Экономия трафика при раздаче интернета

Сделать Windows 10 похожей на Windows 7, XP, 8
Windows

Сделать Windows 10 похожей на Windows 7, XP, 8

Подключение Node.js к MySQL — шаг за шагом
Базы данных

Подключение Node.js к MySQL — шаг за шагом

Синхронизация буфера обмена Windows и Android
Инструкции

Синхронизация буфера обмена Windows и Android

Словарь на Python с Tkinter и PyMultiDictionary
Программирование

Словарь на Python с Tkinter и PyMultiDictionary