Разбор: 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:
- Напечатайте шаблон OpenCV (шахматная доска 6×9 внутренних углов) на А4 без масштабирования.
- Подключите дрон и запустите
python3 camera_calibration.py; откройте потокrtsp://10.42.0.1:8889/calibration/. - Сделайте 10–15 снимков с разных ракурсов: в терминале вводите
1+ Enter для снимка; когда снимков достаточно —q+ Enter. - Проверьте найденные углы в потоке
rtsp://10.42.0.1:8889/calibration_result/. - Дождитесь сохранения
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 читают этот файл.