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

Видео стабилизация — это приём, снижающий нежелательные перемещения и вибрации в видеозаписи. Съёмка с рук, вибрация и движение объектов приводят к неустойчивым кадрам. Стабилизация преобразует последовательность кадров, чтобы итоговое видео выглядело плавнее.
Основная цель стабилизации — оценить движение камеры между соседними кадрами и применить преобразования, выравнивающие кадры. Это минимизирует воспринимаемое колебание.
Настройка окружения
Рекомендуется создать виртуальное окружение, чтобы зависимости проекта не конфликтовали с системными пакетами. Установите нужные библиотеки:
pip install opencv-python numpyNumPy отвечает за численные операции, 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()Выходной ролик показывает сравнение дрожащего и стабилизированного видео.
Советы по параметрам и оптимизациям
- 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)
- Проверьте качество входного видео и метаданные (ширина, высота, fps).
- Настройте параметры детекции точек (maxCorners, qualityLevel).
- Протестируйте вычисление трансформаций на небольшом отрезке (200–500 кадров).
- Сгладьте траекторию, оцените визуально и по метрикам (снижение спектра трансформаций).
- Обработайте всё видео, проверьте итог и отрегулируйте параметры.
Совместимость и замечания
- Код основан на стандартных функциях 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: для видеопотоков в реальном времени требуется иная архитектура — буферизация и апроксимации с ограниченным окном кадра.
Похожие материалы
Установка MS SQL Server на Ubuntu 20.04
Экономия трафика при раздаче интернета
Сделать Windows 10 похожей на Windows 7, XP, 8
Подключение Node.js к MySQL — шаг за шагом
Синхронизация буфера обмена Windows и Android