Разбор: camera_examples

← Все разборы примеров

Источник: репозиторий pioneer-team/pioneer-sdk2-example (GitFlic). Справочник по классам API — на странице «Pioneer SDK 2».

Что внутри и запуск

  • get_frames_from_camera.py — бесконечный цикл кадров + RTSP-поток video.
  • camera_stream.py — то же самое короче, с таймером на 30 с.
  • camera_calibration.py — калибровка камеры по шахматной доске, результат в data.yml.
  • set_camera_angle.py — поворот сервопривода камеры.
  • take_photo_angles.py — фото при углах −25°, 0° и 25°.

Примеры с кадрами и ImageViewer запускаются на борту Мини 2; потоки открываются на ПК (VLC, ffplay) по адресам вида rtsp://10.42.0.1:8889/video/.

get_frames_from_camera.py — базовый цикл кадра

Компактный пример знакомит с двумя классами SDK: Camera и ImageViewer:

from pioneer_sdk2 import Camera, ImageViewer          # импортируем классы Camera и ImageViewer из библиотеки pioneer_sdk2

camera = Camera()                                      # создаем экземпляр класса Camera
viewer = ImageViewer()                                 # создаем экземпляр класса ImageViewer

try:                                                    # основной код находится внутри блока try
    while True:                                         # запускаем бесконечный цикл
        frame = camera.get_cv_frame(timeout=5.0)        # сохраняем изображение в переменную frame
                                                        # timeout=5.0 - время ожидания кадра в секундах

        if frame is not None:                           # проверяем, что изображение получено
            viewer.imshow("video", frame, fps=30)       # отправляем изображение в RTSP-трансляцию

except KeyboardInterrupt:                               # если пользователь остановил программу сочетанием Ctrl+C
    print("Остановка программы")                        # выводим сообщение об остановке программы

finally:                                                # блок finally выполнится при завершении программы
    viewer.close()                                      # останавливаем RTSP-трансляцию
    camera.stop()                                       # закрываем передачу кадров

Ключевые места:

  • Camera() подключается к бортовой камере; get_cv_frame(timeout=5.0) возвращает кадр OpenCV (BGR) или None по таймауту.
  • ImageViewer() — RTSP-издатель: imshow(name, frame, fps) публикует кадр в поток с заданным именем. Адрес состоит из IP дрона (по умолчанию 10.42.0.1 — это адрес хот-спота), порта 8889 и имени потока — первого аргумента imshow.
  • viewer.close() и camera.stop() в finally обязательны: камера и RTSP-сервер не останавливаются сами.

camera_stream.py — вариант с таймером

Тот же цикл, но без try/finally: перед стартом запоминается время, длительность управляется переменной cycle_time:

viewer = ImageViewer()                # создаем экземпляр класса ImageViewer
camera = Camera()                     # создаем экземпляр класса Camera

my_time = time.time()                        # объявляем переменную my_time, присваиваем текущее время (в секундах с 1970 года)
cycle_time = 30                              # объявляем переменную cycle_time, присваиваем желаемую длительность цикла

while time.time() - my_time < cycle_time:        # запускаем цикл, код будет повторяться, пока условие верно
    frame = camera.get_cv_frame()                # получаем кадр и присваиваем данные переменной frame
    viewer.imshow("video", frame, fps=30)        # запускаем трансляцию, содержит аргументы:
                                                 # video - название трансляции
                                                 # frame - переменная с ранее полученным кадром
                                                 # fps=30 - количество кадров в секунду при передаче видео

                                                 # трансляция выполняется по адресу: 10.42.0.1:8889/video

Условие time.time() - my_time < cycle_time — простая ограниченная по времени трансляция (30 с); после выхода viewer.close() останавливает поток.

set_camera_angle.py — сервокамера

Подвес камеры Мини 2 качается в диапазоне ±25°. Класс ServoCamera скрывает протокол и позволяет повернуть камеру одной командой:

from pioneer_sdk2 import ServoCamera # импортируем класс ServoCamera из библиотеки pioneer_sdk2
import time                          # библиотека time содержит функции для работы со временем

servo_drone_1 = ServoCamera()        # создаем экземпляр класса ServoCamera, проверяет поддержку сервомотора

servo_drone_1.set_angle(25)  # устанавливаем угол камеры на 25 градусов
time.sleep(3)                # ставим паузу на 3 секунды
servo_drone_1.set_angle(-25) # устанавливаем угол камеры на -25 градусов

set_angle(25) — камера вверх, set_angle(-25) — вниз. Углы в градусах; между командами нужна пауза, чтобы сервопривод успел отработать.

take_photo_angles.py — фото по углам

Комбинация двух классов выше: повернуть камеру, дождаться её стабилизации, снять кадр и сохранить JPG:

def take_photo_at_angle(angle):     # функция поворачивает камеру на нужный угол и сохраняет фото
    servo_camera.set_angle(angle)   # устанавливаем угол поворота камеры

    time.sleep(3)                   # ждём 3 секунды, чтобы камера успела повернуться

    frame = camera.get_cv_frame(timeout=5.0) # получаем один кадр с камеры

    if frame is None:               # проверяем, удалось ли получить кадр
        print(f"Не удалось получить фото при угле {angle}") # выводим сообщение об ошибке
        return                      # выходим из функции, если кадр не получен

    file_name = f"photo_angle_{angle}.jpg" # задаём имя файла для фотографии
    cv2.imwrite(file_name, frame)          # сохраняем кадр в файл

    print(f"Фото сохранено: {file_name}") # выводим сообщение об успешном сохранении


take_photo_at_angle(-25)        # делаем фото при угле камеры -25 градусов
take_photo_at_angle(0)          # делаем фото при угле камеры 0 градусов
take_photo_at_angle(25)         # делаем фото при угле камеры 25 градусов

camera.stop()                   # останавливаем работу камеры

После set_angle(angle) обязательно time.sleep(3) — камера должна успеть установиться, и только затем кадр сохраняется через cv2.imwrite(...).

camera_calibration.py — калибровка по шахматной доске

Скрипт создаёт data.yml — файл с параметрами камеры, без которого координаты ArUco-меток считаются неточно. Порядок работы из README:

  1. Напечатайте шаблон OpenCV (шахматная доска 6×9 внутренних углов) на А4 без масштабирования.
  2. Подключите дрон и запустите python3 camera_calibration.py; откройте поток rtsp://10.42.0.1:8889/calibration/.
  3. Сделайте 10–15 снимков с разных ракурсов: в терминале вводите 1 + Enter для снимка; когда снимков достаточно — q + Enter.
  4. Проверьте найденные углы в потоке rtsp://10.42.0.1:8889/calibration_result/.
  5. Дождитесь сохранения data.yml рядом со скриптом.

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

def calibrate(images, viewer=None):                         # функция для калибровки камеры по снимкам шахматной доски
    CHECKERBOARD = (6, 9)                                   # количество внутренних углов шахматной доски
                                                            # 6 - количество углов по одной стороне, 9 - по другой

    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # критерий точности поиска углов
                                                            # 30 - максимальное количество итераций
                                                            # 0.001 - требуемая точность
    calibration_flags = (                                   # флаги для поиска углов шахматной доски
        cv2.CALIB_CB_ADAPTIVE_THRESH                        # используем адаптивную обработку изображения
        + cv2.CALIB_CB_FAST_CHECK                           # ускоряем проверку наличия шахматной доски
        + cv2.CALIB_CB_NORMALIZE_IMAGE                      # нормализуем изображение для лучшего поиска углов
    )

Углы ищутся с флагами ADAPTIVE_THRESH/FAST_CHECK/NORMALIZE_IMAGE, критерий уточняет позиции до 0,001. Итоговая калибровка — классический cv2.calibrateCamera:

    if image_size is None or not imgpoints:                 # проверяем, что есть данные для калибровки
        raise RuntimeError("Не удалось найти углы шахматной доски для калибровки")

    camera_matrix = np.zeros((3, 3), np.float64)             # матрица камеры будет рассчитана OpenCV
    dist_coeffs = np.zeros((5, 1), np.float64)               # коэффициенты искажений будут рассчитаны OpenCV

    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(      # выполняем калибровку камеры
        objpoints,                                          # передаем 3D-точки шахматной доски
        imgpoints,                                          # передаем 2D-точки на изображениях
        image_size,                                         # передаем размер изображения
        camera_matrix,                                      # передаем матрицу камеры для заполнения
        dist_coeffs                                         # передаем коэффициенты искажений для заполнения
    )

    return mtx, dist                                        # возвращаем матрицу камеры и коэффициенты искажений

Матрица и коэффициенты сохраняются через cv2.FileStorage в data.yml; файл лежит рядом со скриптом (Path(__file__).with_name("data.yml")), поэтому примеры можно запускать из любого каталога. Скрипты aruco_examples читают этот файл.