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

Стабилизация видео с 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
Автор
Редакция

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

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 — руководство