Стабилизация видео с помощью OpenCV: пошаговый пример на Python
Важно: пример рассчитан на видеопотоки без дополнительной информации с датчиков. Для съёмки с IMU/гимбалом нужны другие подходы.
Зачем нужна стабилизация видео
Стабилизация уменьшает нежелательное движение и дрожание в видеороликах. При съёмке с рук, вибрации или резком перемещении камеры кадры получаются неустойчивыми. Цель стабилизации — оценить реальное движение камеры между соседними кадрами и применить преобразования, которые выровняют кадры и минимизируют заметное смещение для зрителя.
Краткое определение: стабилизация видео — процесс оценки движения камеры и компенсации этого движения с помощью геометрических преобразований кадров.
Основные идеи алгоритма (в одной строке)
- Найти соответствия точек между соседними кадрами, 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()Пример выходного изображения
На выходе вы получите видео, где слева — исходный, справа — стабилизированный кадр.
Когда этот метод чувствует себя плохо — ограничения и крайние случаи
- Очень сильная потеря текстуры или размытие движения: детекторы точек не найдут устойчивых сопоставлений.
- Быстрые резкие тряски и смены кадра (например, прыжок камеры): сглаживание не успеет, и результат будет прыгать.
- Объекты крупным планом двигаются независимо от камеры: локальные движения могут быть ошибочно интерпретированы как движение камеры.
- Панорамная съёмка с очень большим зумом: аффинная модель может не описывать параллакс и перспективные искажения.
Важно учитывать, что обработка основана на 2D-результатах: для сложной трёхмерной стабилизации требуются дополнительные модели или данные IMU.
Альтернативные подходы и инструменты
- Использовать библиотеки высокого уровня: vidstab (Python), которые реализуют похожие пайплайны и удобные API.
- Применять глобальные методы на основе гомографий и RANSAC — полезно при плоских сценах.
- Комбинировать визуальные данные с IMU/гироскопом — даёт лучшую точность для мобильных устройств и дронов.
- Использовать коммерческие решения или встроенные механизмы в видеоредакторах (DaVinci Resolve, Adobe Premiere).
Мини‑методология: шаги для воспроизводимой реализации
- Подготовка: виртуальное окружение, входное видео, тестовый набор кадров.
- Обнаружение точек: goodFeaturesToTrack с проверкой плотности точек.
- Отслеживание: calcOpticalFlowPyrLK с фильтрацией по статусу и ошибке.
- Оценка трансформаций: estimateAffine2D + контроль на NaN/неуспех.
- Накопление и сглаживание: cumsum → moving average.
- Применение: warpAffine → fix_border → запись.
- Валидация: визуальная оценка «до/после», проверка 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, использование более сложных моделей движения или готовых библиотек.
Примечание: экспериментируйте с параметрами, логируйте промежуточные результаты и добавляйте обработку ошибок для робастности в продакшне.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone