Arduino + OpenCV Python | Комплексный курс
Содержание
Введение
Курс-конспект по компьютерному зрению с Arduino! Данный курс разработан для всех, кто хочет освоить основы компьютерного зрения и научиться создавать практические проекты, сочетающие в себе возможности микроконтроллеров и современных библиотек обработки изображений. Этот курс подойдет как начинающим, так и опытным разработчикам, желающим расширить свои навыки в области интернета вещей (IoT) и машинного зрения.
Цель курса: предоставить фундаментальные знания и практический опыт для создания собственных проектов, включая управление лампами жестами, PID-трекинг лиц, определение углов, сортировку цветов с конвейерной лентой и многое другое.
Уникальность подхода: в отличие от традиционных курсов по компьютерному зрению, которые сосредоточены только на программной части, мы интегрируем физические устройства (Arduino) с программными решениями (OpenCV), что позволяет создавать законченные, функциональные системы.
Предварительные требования: базовые знания программирования на любом языке, знакомство с основами электротехники будет преимуществом, но не обязательно.
↑ К оглавлениюГлава 1: Основы Arduino
1.1 Введение в микроконтроллеры
Микроконтроллер — это компактная интегральная схема, предназначенная для управления электронными устройствами. Он содержит процессор, память (как оперативную, так и постоянную) и программируемые порты ввода/вывода на одном кристалле. В отличие от микропроцессоров (которые используются в персональных компьютерах), микроконтроллеры предназначены для встраиваемых систем и обладают встроенными периферийными устройствами.
Arduino — это не просто плата с микроконтроллером, а целая экосистема, включающая аппаратное обеспечение (различные модели плат), программное обеспечение (интегрированная среда разработки IDE) и сообщество пользователей. Основное преимущество Arduino заключается в простоте программирования благодаря использованию высокоуровневых функций и библиотек.
Популярные модели Arduino:
- Arduino Uno — наиболее распространенная модель, идеальная для начинающих:
- Микроконтроллер: ATmega328P
- Рабочее напряжение: 5V
- Цифровые входы/выходы: 14 (из которых 6 могут использоваться как ШИМ-выходы)
- Аналоговые входы: 6
- Память: 32 КБ флеш-памяти, 2 КБ SRAM, 1 КБ EEPROM
- Arduino Mega — плата с расширенными возможностями для более сложных проектов:
- Микроконтроллер: ATmega2560
- Цифровые входы/выходы: 54 (из которых 15 могут использоваться как ШИМ-выходы)
- Аналоговые входы: 16
- Память: 256 КБ флеш-памяти, 8 КБ SRAM, 4 КБ EEPROM
- Arduino Nano/Mini — компактные версии для проектов с ограниченным пространством:
- Сохраняют большинство функций Uno в меньшем форм-факторе
- Часто используются в готовых устройствах и прототипах
Важное отличие: Arduino производит не сами микроконтроллеры, а платы на их основе. Особенность Arduino заключается в программном обеспечении, которое значительно упрощает программирование по сравнению с другими платформами, где требуется более глубокое понимание аппаратного уровня.
1.2 Цифровые и аналоговые сигналы
Понимание различий между цифровыми и аналоговыми сигналами является фундаментальным для работы с микроконтроллерами.
Цифровой сигнал — сигнал, который может принимать только два дискретных значения:
- LOW (низкий уровень, 0) — обычно соответствует напряжению 0V
- HIGH (высокий уровень, 1) — обычно соответствует напряжению 5V (или 3.3V для плат с таким напряжением)
Характеристики цифровых сигналов:
- Простота обработки и передачи
- Устойчивость к помехам (при условии достаточного запаса по напряжению между уровнями)
Аналоговый сигнал — сигнал, который может принимать бесконечное число значений в определенном диапазоне. В контексте Arduino, должен оцифровываться аналогово-цифровым преобразователем (АЦП / ADC):
1. Аналоговый вход (для чтения данных с датчиков):
- Разрешение: 10 бит (2^10 = 1024 дискретных значений)
- Диапазон: от 0 до 1023
- Соответствие напряжению: 0 = 0V, 1023 = 5V (или опорное напряжение)
- Примеры использования: потенциометры, датчики температуры, датчики освещенности
2. Аналоговый выход (технически — ШИМ, Pulse Width Modulation):
- Разрешение: 8 бит (2^8 = 256 дискретных значений)
- Диапазон: от 0 до 255
- Принцип работы: изменение скважности (отношения периода следования импульсов к их длительности) для имитации аналогового сигнала
- Примеры использования: управление яркостью светодиодов, скоростью двигателей
Практический пример: управление вентилятором
- Цифровое управление: вентилятор либо включен на максимальной скорости, либо выключен
- Аналоговое управление (ШИМ): позволяет плавно регулировать скорость вращения вентилятора от 0 (выключен) до 255 (максимальная скорость)
1.3 Структура Arduino программы
Каждая программа для Arduino (называемая "скетч") имеет строго определенную структуру, которая состоит из трех основных разделов:
/**
* Структура Arduino скетча
* Каждый скетч должен содержать как минимум две функции:
* setup() - выполняется один раз при запуске
* loop() - выполняется циклически после setup()
*/
// Раздел 1: Объявления и инициализации
// Здесь объявляются глобальные переменные, константы и подключаются библиотеки
int ledPin = 13; // Переменная для хранения номера пина светодиода
const int delayTime = 1000; // Константа для хранения времени задержки (в миллисекундах)
// Раздел 2: Функция setup()
// Выполняется один раз при запуске Arduino или после нажатия кнопки сброса
void setup() {
// Настройка пина 13 как выход (OUTPUT)
// Это означает, что Arduino будет подавать напряжение на этот пин
pinMode(ledPin, OUTPUT);
// Инициализация последовательной связи
// 9600 - скорость передачи данных в бодах (бит в секунду)
Serial.begin(9600);
// Вывод сообщения в монитор порта
Serial.println("Программа запущена!");
}
// Раздел 3: Функция loop()
// Выполняется циклически после завершения setup()
// Это основная логика программы
void loop() {
// Включение светодиода (подача HIGH уровня на пин 13)
digitalWrite(ledPin, HIGH);
// Вывод состояния в монитор порта
Serial.println("LED включен");
// Задержка на 1000 миллисекунд (1 секунда)
// В течение задержки программа "замирает" - следующий код не выполняется
delay(delayTime);
// Выключение светодиода (подача LOW уровня на пин 13)
digitalWrite(ledPin, LOW);
// Вывод состояния в монитор порта
Serial.println("LED выключен");
// Еще одна задержка на 1 секунду
delay(delayTime);
// После выполнения последней строки loop() начинается снова с первой строки
// Это продолжается бесконечно, пока Arduino включен
}
Ключевые моменты структуры:
- Глобальные объявления — переменные, объявленные вне функций, доступны во всем коде
- Функция setup() — место для однократных настроек и инициализаций
- Функция loop() — основной цикл программы, выполняется непрерывно
- Комментарии — важны для понимания кода, начинаются с
//для однострочных или/* */для многострочных
Глава 2: Настройка среды разработки
2.1 Установка Arduino IDE
Arduino IDE (Integrated Development Environment) — это официальная среда разработки для программирования плат Arduino. Она предоставляет все необходимые инструменты для написания, компиляции и загрузки кода на плату.
Пошаговая инструкция по установке:
- Скачивание Arduino IDE:
- Перейдите на официальный сайт arduino.cc
- Выберите версию, соответствующую вашей операционной системе (Windows, macOS, Linux)
- Рекомендуется скачать версию 1.8.x или выше
- Установка на Windows:
- Запустите скачанный установочный файл (.exe)
- Примите лицензионное соглашение
- Выберите компоненты для установки (рекомендуется оставить все по умолчанию)
- Выберите путь установки (рекомендуется оставить по умолчанию)
- Дождитесь завершения установки
- После установки подключите Arduino к компьютеру через USB-кабель
- Установка на macOS:
- Откройте скачанный архив (.zip)
- Перетащите приложение Arduino в папку "Программы"
- При первом запуске может потребоваться разрешить запуск приложения в настройках безопасности
- Установка на Linux:
- Распакуйте архив в выбранную директорию
- Запустите скрипт установки или используйте менеджер пакетов вашего дистрибутива
- Настройка драйверов:
- Windows обычно автоматически устанавливает необходимые драйверы
- При возникновении проблем можно установить драйверы вручную с сайта производителя
- Проверка подключения:
- Откройте Arduino IDE
- Перейдите в меню "Инструменты" → "Порт"
- Должен появиться порт с названием "Arduino" (например, COM3 на Windows или /dev/ttyUSB0 на Linux)
2.2 Установка библиотеки CVZone
Библиотека CVZone — это специальная библиотека, которая упрощает обмен данными между Arduino и Python, особенно для проектов компьютерного зрения. Она обрабатывает последовательную связь и форматирование данных.
Пошаговая инструкция по установке:
- Скачивание библиотеки:
- Библиотеку можно найти на GitHub или других ресурсах
- Скачайте файл в формате ZIP (обычно называется "CVZone.zip" или подобное)
- Установка через Arduino IDE:
- Откройте Arduino IDE
- Перейдите в меню "Скетч" → "Подключить библиотеку" → "Добавить .ZIP библиотеку..."
- В открывшемся диалоговом окне выберите скачанный ZIP-файл
- Нажмите "Открыть"
- Проверка установки:
- Перейдите в меню "Файл" → "Примеры"
- Прокрутите список вниз до раздела "CVZone" (может находиться в "Недавно добавленных")
- Если библиотека установлена корректно, вы увидите список примеров
- Альтернативный способ установки (через менеджер библиотек):
- В Arduino IDE перейдите в "Инструменты" → "Управлять библиотеками..."
- В поиске введите "CVZone"
- Если библиотека доступна в официальном менеджере, установите ее оттуда
- Проверка работы библиотеки:
- Откройте пример из меню "Примеры" → "CVZone" → "Basic"
- Скомпилируйте скетч (кнопка с галочкой)
- Если компиляция прошла успешно, библиотека установлена правильно
2.3 Настройка Python среды
Для работы с OpenCV и Arduino в Python необходимо настроить окружение и установить необходимые библиотеки.
Пошаговая инструкция:
- Установка Python:
- Скачайте Python с официального сайта python.org
- Рекомендуется версия 3.8 или выше
- При установке на Windows отметьте опцию "Add Python to PATH"
- Проверка установки Python:
# Откройте командную строку (терминал) python --version # Должна отобразиться установленная версия PythonBash - Создание виртуального окружения (рекомендуется):
# Создание виртуального окружения python -m venv arduino_cv_env # Активация на Windows arduino_cv_env\Scripts\activate # Активация на macOS/Linux source arduino_cv_env/bin/activateBash - Установка необходимых библиотек:
# Обновите pip (менеджер пакетов Python) pip install --upgrade pip # Установите основные библиотеки pip install opencv-python # OpenCV для компьютерного зрения pip install numpy # Библиотека для работы с массивами и математическими операциями pip install pyserial # Для последовательной связи с Arduino # Установите CVZone (специальная библиотека для интеграции с Arduino) # Обратите внимание: имя пакета может отличаться pip install cvzone # Для некоторых функций может потребоваться mediapipe pip install mediapipeBash - Проверка установки библиотек:
# Создайте тестовый скрипт test_install.py import cv2 import numpy as np import cvzone import serial print("OpenCV версия:", cv2.__version__) print("NumPy версия:", np.__version__) print("Все библиотеки успешно импортированы!")Python - Настройка редактора кода (рекомендуемые варианты):
- Visual Studio Code с расширениями Python и Arduino
- PyCharm (Community Edition бесплатен)
- Jupyter Notebook для интерактивной работы
- Проверка связи с Arduino:
- Загрузите простой скетч на Arduino (например, из примеров CVZone)
- Запустите тестовый скрипт Python для проверки связи
Глава 3: Базовые проекты с LED
3.1 Управление встроенным LED через Arduino
Теория: Каждая плата Arduino Uno имеет встроенный светодиод (LED), подключенный к цифровому пину 13. Этот LED удобно использовать для тестирования кода без подключения внешних компонентов.
/**
* LED Basic - управление встроенным светодиодом на пине 13
*
* Пин 13 на Arduino Uno имеет встроенный светодиод и резистор,
* поэтому дополнительные компоненты не требуются.
*
* Принцип работы:
* 1. Настройка пина 13 как выходного порта в функции setup()
* 2. В цикле loop() попеременная подача HIGH и LOW уровня на пин
* 3. HIGH включает светодиод (5V), LOW выключает (0V)
*
* Цикл: 1 секунда включен, 1 секунда выключен
*
* Компоненты:
* - Arduino Uno
* - USB кабель для подключения и питания
*
* Схема подключения:
* Не требуется - используется встроенный светодиод
*/
// Объявление константы для пина LED
// Использование const позволяет избежать случайного изменения значения
const int LED_PIN = 13; // Пин 13 имеет встроенный светодиод
// Объявление константы для времени задержки
const int DELAY_TIME = 1000; // 1000 миллисекунд = 1 секунда
// Функция setup() выполняется один раз при запуске
void setup() {
// Настройка пина 13 как выход (OUTPUT)
// Это означает, что Arduino будет управлять напряжением на этом пине
pinMode(LED_PIN, OUTPUT);
// Инициализация последовательной связи для отладки
Serial.begin(9600); // 9600 бод - стандартная скорость
Serial.println("Программа управления LED запущена");
}
// Функция loop() выполняется бесконечно после setup()
void loop() {
// Этап 1: Включение светодиода
digitalWrite(LED_PIN, HIGH); // Подача 5V на пин 13
Serial.println("LED ВКЛЮЧЕН"); // Вывод состояния в монитор порта
delay(DELAY_TIME); // Ожидание 1000 мс (1 секунда)
// Этап 2: Выключение светодиода
digitalWrite(LED_PIN, LOW); // Подача 0V на пин 13
Serial.println("LED ВЫКЛЮЧЕН"); // Вывод состояния в монитор порта
delay(DELAY_TIME); // Ожидание 1000 мс (1 секунда)
// После выполнения последней строки функция loop() начинается сначала
// Таким образом создается бесконечный цикл: включение-пауза-выключение-пауза
}
Подробное объяснение кода:
- Константы: использование
constдля объявления констант делает код более читаемым и предотвращает случайное изменение значений. - Функция
pinMode(): настраивает указанный пин как вход (INPUT) или выход (OUTPUT). Для управления устройствами (как LED) используется OUTPUT. - Функция
digitalWrite(): устанавливает на указанном пине высокий (HIGH, ~5V) или низкий (LOW, 0V) уровень. - Функция
delay(): приостанавливает выполнение программы на указанное количество миллисекунд. Внимание: во время delay() программа не выполняет другой код! - Последовательный порт (Serial): используется для отладки и мониторинга состояния программы.
Подключение внешнего LED (если требуется):
Схема подключения внешнего светодиода:
Arduino Pin 13 → Резистор 220 Ом → Анод LED (+) → Катод LED (-) → GND Arduino
Важные моменты:
1. Светодиод имеет полярность: длинная ножка - анод (+), короткая - катод (-)
2. Резистор 220 Ом необходим для ограничения тока и предотвращения перегорания LED
3. Резистор можно подключать как перед анодом, так и после катода
3.2 Управление LED через Python с CVZone
Теория: В этом проекте мы создаем систему, где Python скрипт отправляет команды на Arduino для управления светодиодом. Это демонстрирует принцип взаимодействия между высокоуровневым языком (Python) и микроконтроллером (Arduino).
Arduino код (скетч):
/**
* LED Serial - управление LED через последовательный порт
*
* Этот скетч позволяет управлять LED с помощью команд,
* отправляемых из Python через библиотеку CVZone.
*
* Принцип работы:
* 1. Arduino ожидает данные от Python через последовательный порт
* 2. Данные приходят в формате: $[значение] (например, $1 или $0)
* 3. Библиотека CVZone парсит данные и извлекает числовые значения
* 4. Полученное значение используется для управления светодиодом
*
* Формат команды: $[значение]
* Пример: $1 (включить), $0 (выключить)
*
* Компоненты:
* - Arduino Uno
* - USB кабель
* - Светодиод (опционально, можно использовать встроенный на пине 13)
*
* Подключение:
* LED на пин 13 (встроенный) или внешний LED по схеме из предыдущего раздела
*/
// Подключение библиотеки CVZone для упрощения работы с последовательным портом
#include "CVZone.h"
// Инициализация объекта SerialData для приема данных
// Параметры конструктора:
// 1) Количество принимаемых значений (в данном случае 1 - статус LED)
// 2) Количество цифр в каждом значении (1 - так как значения 0 или 1)
SerialData serialData(1, 1);
// Массив для хранения полученных значений
// Размер массива должен соответствовать первому параметру конструктора SerialData
int receivedValues[1];
// Функция setup() выполняется один раз при запуске
void setup() {
// Настройка пина 13 как выход
pinMode(13, OUTPUT);
// Начальное состояние - LED выключен
digitalWrite(13, LOW);
// Инициализация последовательной связи с библиотекой CVZone
serialData.begin();
// Для отладки можно также инициализировать стандартный Serial
Serial.begin(9600);
Serial.println("Arduino готов к приему команд из Python");
}
// Функция loop() выполняется бесконечно
void loop() {
// Получение данных из последовательного порта
// Библиотека CVZone автоматически парсит входящие данные
// и заполняет массив receivedValues полученными числами
serialData.getData(receivedValues);
// Проверка, получены ли новые данные
// receivedValues[0] содержит первое (и единственное) значение
// Значение сохраняется до получения новых данных
// Управление LED на основе полученного значения
// Если получено 1 - включаем LED, если 0 - выключаем
if (receivedValues[0] == 1) {
digitalWrite(13, HIGH); // Включить LED
} else if (receivedValues[0] == 0) {
digitalWrite(13, LOW); // Выключить LED
}
// Небольшая задержка для стабильности
// Без delay() цикл выполняется слишком быстро, что может привести
// к высокой загрузке процессора и нестабильной работе
delay(10);
}
Python код:
"""
LED_Control.py - управление LED через Python
Этот скрипт демонстрирует отправку команд на Arduino
для управления светодиодом с использованием библиотеки CVZone.
Принцип работы:
1. Установка соединения с Arduino через последовательный порт
2. Отправка команд в формате, понятном скетчу на Arduino
3. Получение ответа (если требуется)
Последовательность действий:
1. Импорт необходимых библиотек
2. Создание объекта для связи с Arduino
3. Отправка команд в цикле
Требования:
- Установленные библиотеки: cvzone, pyserial
- Загруженный скетч LED_Serial на Arduino
- Подключенный Arduino к компьютеру
Примечание:
Скрипт автоматически определяет порт Arduino.
Если это не работает, укажите порт вручную:
arduino = SerialObject("COM3") # Для Windows
arduino = SerialObject("/dev/ttyUSB0") # Для Linux
"""
# Импорт необходимых библиотек
from cvzone.SerialModule import SerialObject # Библиотека для связи с Arduino
from time import sleep # Библиотека для создания задержек (пауз) в программе
# Создание объекта для связи с Arduino
# SerialObject автоматически определяет порт, к которому подключен Arduino
# При возникновении проблем с автоопределением можно указать порт вручную:
# arduino = SerialObject("COM3") # Для Windows, где COM3 - порт Arduino
# arduino = SerialObject("/dev/ttyUSB0") # Для Linux/Mac
arduino = SerialObject()
print("Соединение с Arduino установлено")
print("Управление LED (Ctrl+C для выхода)")
try:
# Бесконечный цикл для отправки команд
# Цикл будет выполняться, пока пользователь не прервет программу (Ctrl+C)
while True:
# Этап 1: Включение LED
# Отправка команды [1] на Arduino
# Arduino интерпретирует это как команду включить светодиод
arduino.sendData([1])
print("LED: ВКЛ") # Вывод состояния в консоль
# Пауза 3 секунды - LED будет гореть все это время
# Функция sleep() приостанавливает выполнение программы на указанное количество секунд
sleep(3)
# Этап 2: Выключение LED
# Отправка команды [0] на Arduino
arduino.sendData([0])
print("LED: ВЫКЛ") # Вывод состояния в консоль
# Пауза 1 секунда перед следующим циклом
sleep(1)
# После паузы цикл начинается снова - LED снова включится
# Таким образом создается цикл: 3 секунды горит, 1 секунда не горит
# Обработка прерывания клавишами Ctrl+C
# Это стандартный способ корректного завершения программы в Python
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем")
# Важно: перед выходом выключаем LED
arduino.sendData([0]) # Отправляем команду на выключение LED
print("LED выключен")
# Блок finally выполняется в любом случае, даже если произошла ошибка
finally:
print("Очистка ресурсов...")
# Дополнительные действия по очистке (если необходимы)
# Например, закрытие файлов, освобождение ресурсов камеры и т.д.
3.3 Добавление визуализации с OpenCV
Теория: В этом разделе мы расширяем функциональность, добавляя графический интерфейс с использованием OpenCV. Это демонстрирует, как можно создавать пользовательские интерфейсы для проектов компьютерного зрения.
"""
LED_Graphics.py - управление LED с графической визуализацией
Этот скрипт расширяет базовый пример, добавляя визуальное
отображение состояния LED с помощью OpenCV.
Особенности:
1. Использование изображений для визуализации состояния LED
2. Графический интерфейс с помощью OpenCV
3. Интеграция управления аппаратными компонентами (Arduino) с графикой
Требования:
- Установленные библиотеки: cvzone, opencv-python, numpy
- Изображения LED_on.jpg и LED_off.jpg в папке resources
- Загруженный скетч LED_Serial на Arduino
"""
# Импорт необходимых библиотек
import cv2 # OpenCV - библиотека компьютерного зрения и работы с изображениями
from cvzone.SerialModule import SerialObject # Для связи с Arduino
from time import sleep # Для создания задержек
# Создание объекта для связи с Arduino
# SerialObject автоматически определяет порт Arduino
arduino = SerialObject()
print("Загрузка изображений...")
# Загрузка изображений для визуализации состояния LED
# Обратите внимание на путь: "../resources/" означает переход на один уровень вверх
# от текущей папки, затем вход в папку "resources"
try:
# Изображение включенного светодиода
img_led_on = cv2.imread("../resources/LED_on.jpg")
# Изображение выключенного светодиода
img_led_off = cv2.imread("../resources/LED_off.jpg")
# Проверка успешной загрузки изображений
if img_led_on is None:
print("Ошибка: не удалось загрузить изображение LED_on.jpg")
print("Убедитесь, что файл существует по пути: ../resources/LED_on.jpg")
# Создаем черное изображение в качестве заглушки
img_led_on = np.zeros((300, 300, 3), dtype=np.uint8)
cv2.putText(img_led_on, "LED ON", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
if img_led_off is None:
print("Ошибка: не удалось загрузить изображение LED_off.jpg")
img_led_off = np.zeros((300, 300, 3), dtype=np.uint8)
cv2.putText(img_led_off, "LED OFF", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
except Exception as e:
print(f"Ошибка при загрузке изображений: {e}")
exit(1)
print("Изображения загружены успешно")
print("Запуск управления LED с графическим интерфейсом...")
print("Нажмите 'q' в окне изображения для выхода")
try:
# Основной цикл программы
# Будет выполняться, пока пользователь не закроет окно или не нажмет 'q'
while True:
# Этап 1: Включение LED и отображение соответствующего изображения
# Отправка команды на включение LED (значение 1)
arduino.sendData([1])
print("LED: ВКЛ")
# Отображение изображения включенного LED
# cv2.imshow() создает окно с указанным именем и отображает в нем изображение
cv2.imshow("LED Status", img_led_on)
# cv2.waitKey() ожидает нажатия клавиши в течение указанного времени (в миллисекундах)
# 1000 мс = 1 секунда
# Функция также обрабатывает события окна (обновление изображения, закрытие и т.д.)
# Если нажата клавиша 'q', выходим из цикла
if cv2.waitKey(1000) & 0xFF == ord('q'):
print("Выход по команде пользователя (клавиша 'q')")
break
# Этап 2: Выключение LED и отображение соответствующего изображения
# Отправка команды на выключение LED (значение 0)
arduino.sendData([0])
print("LED: ВЫКЛ")
# Отображение изображения выключенного LED
cv2.imshow("LED Status", img_led_off)
# Ожидание 1 секунды с обработкой событий
if cv2.waitKey(1000) & 0xFF == ord('q'):
print("Выход по команде пользователя (клавиша 'q')")
break
# Цикл продолжается: снова включение, пауза, выключение, пауза...
# Обработка прерывания клавишами Ctrl+C в консоли
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем (Ctrl+C)")
# Блок finally выполняется всегда, даже если произошла ошибка
finally:
print("Завершение программы...")
# Важно: выключаем LED перед выходом
arduino.sendData([0])
print("LED выключен")
# Закрытие всех окон OpenCV
# cv2.destroyAllWindows() закрывает все открытые окна OpenCV
cv2.destroyAllWindows()
# Дополнительно: принудительное закрытие окон (на случай, если destroyAllWindows не сработал)
cv2.waitKey(1) # Короткое ожидание для обработки команд закрытия
cv2.waitKey(1)
cv2.waitKey(1)
print("Программа завершена")
Расширенная версия с дополнительными функциями:
"""
LED_Graphics_Advanced.py - расширенная версия с дополнительными функциями
Дополнительные возможности:
1. Отображение таймера
2. Счетчик циклов
3. Возможность управления длительностью через графический интерфейс
"""
import cv2
import numpy as np
from cvzone.SerialModule import SerialObject
from time import sleep, time
# Инициализация
arduino = SerialObject()
# Создание изображений программно (вместо загрузки из файлов)
def create_led_image(state, width=400, height=300):
"""Создает изображение светодиода с указанным состоянием"""
# Создаем черное изображение
img = np.zeros((height, width, 3), dtype=np.uint8)
# Рисуем круг (светодиод)
center = (width // 2, height // 2 - 30)
radius = 80
if state == "ON":
# Зеленый круг для включенного состояния
color = (0, 255, 0) # BGR: зеленый
status_text = "LED ВКЛЮЧЕН"
text_color = (0, 255, 0)
else:
# Красный круг для выключенного состояния
color = (0, 0, 255) # BGR: красный
status_text = "LED ВЫКЛЮЧЕН"
text_color = (0, 0, 255)
# Рисуем светодиод (круг)
cv2.circle(img, center, radius, color, -1) # -1 означает заливку
# Добавляем текст состояния
cv2.putText(img, status_text, (width//2 - 100, height//2 + 100),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, text_color, 2)
return img
# Создаем изображения
img_led_on = create_led_image("ON")
img_led_off = create_led_image("OFF")
# Параметры управления
on_time = 2 # Время включения (секунды)
off_time = 1 # Время выключения (секунды)
cycle_count = 0 # Счетчик циклов
print("Управление LED с графическим интерфейсом")
print(f"Время включения: {on_time}с, Время выключения: {off_time}с")
print("Нажмите 'q' для выхода, '+'/'-' для изменения времени")
try:
start_time = time() # Запоминаем время начала
while True:
cycle_count += 1
# Включение
arduino.sendData([1])
current_time = time() - start_time
# Создаем изображение с дополнительной информацией
display_img = img_led_on.copy()
info_text = f"Цикл: {cycle_count} | Время: {current_time:.1f}с"
cv2.putText(display_img, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.imshow("LED Control", display_img)
# Ожидание с обработкой клавиш
key = cv2.waitKey(on_time * 1000) & 0xFF
if key == ord('q'):
break
elif key == ord('+'):
on_time = min(5, on_time + 0.5) # Увеличиваем до максимум 5 секунд
print(f"Время включения увеличено до {on_time}с")
elif key == ord('-'):
on_time = max(0.5, on_time - 0.5) # Уменьшаем до минимум 0.5 секунд
print(f"Время включения уменьшено до {on_time}с")
# Выключение
arduino.sendData([0])
display_img = img_led_off.copy()
cv2.putText(display_img, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.imshow("LED Control", display_img)
key = cv2.waitKey(off_time * 1000) & 0xFF
if key == ord('q'):
break
elif key == ord('+'):
off_time = min(5, off_time + 0.5)
print(f"Время выключения увеличено до {off_time}с")
elif key == ord('-'):
off_time = max(0.5, off_time - 0.5)
print(f"Время выключения уменьшено до {off_time}с")
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем")
finally:
arduino.sendData([0])
cv2.destroyAllWindows()
print(f"Итог: выполнено {cycle_count} циклов")
Глава 4: Работа с аналоговыми датчиками
4.1 Подключение и чтение данных с потенциометра
Теория: Потенциометр — это переменный резистор с регулируемым сопротивлением. При повороте ручки изменяется сопротивление между крайними выводами, что позволяет получать аналоговый сигнал, пропорциональный углу поворота.
/**
* Potentiometer_Basic - чтение данных с потенциометра
*
* Потенциометр - переменный резистор, который изменяет сопротивление
* при повороте ручки. Это позволяет получать аналоговые значения,
* пропорциональные углу поворота.
*
* Принцип работы:
* 1. Потенциометр подключен к аналоговому входу Arduino (A0)
* 2. Arduino считывает напряжение на среднем выводе потенциометра
* 3. Напряжение преобразуется в цифровое значение от 0 до 1023
* 4. Значение выводится в монитор последовательного порта
*
* Подключение потенциометра:
* - Левый вывод (если смотреть со стороны ручки) -> GND
* - Средний вывод -> A0 (аналоговый вход)
* - Правый вывод -> 5V
*
* При повороте ручки напряжение на среднем выводе изменяется
* от 0V (полностью влево) до 5V (полностью вправо)
*
* Компоненты:
* - Arduino Uno
* - Потенциометр 10 кОм
* - Соединительные провода
*/
// Объявление переменной для хранения значения с потенциометра
// Используем тип int, так как analogRead() возвращает значения от 0 до 1023
int potValue = 0;
// Переменная для хранения предыдущего значения (для оптимизации вывода)
int lastPotValue = -1; // -1 гарантирует, что первое значение всегда будет выведено
// Функция setup() выполняется один раз при запуске
void setup() {
// Инициализация последовательного порта на скорости 9600 бод
// Это необходимо для вывода данных в монитор порта
Serial.begin(9600);
// Настройка пина A0 как вход (INPUT)
// Хотя аналоговые пины по умолчанию являются входами,
// явное указание делает код более понятным
pinMode(A0, INPUT);
// Вывод приветственного сообщения
Serial.println("Программа чтения потенциометра запущена");
Serial.println("Поворачивайте ручку потенциометра");
Serial.println("Значения от 0 (влево) до 1023 (вправо)");
Serial.println("================================");
}
// Функция loop() выполняется бесконечно
void loop() {
// Чтение аналогового значения с пина A0
// analogRead() возвращает значение от 0 до 1023
// 0 соответствует 0V, 1023 соответствует 5V (или опорному напряжению)
potValue = analogRead(A0);
// Вывод значения только если оно изменилось
// Это уменьшает количество выводимых данных и повышает читаемость
if (potValue != lastPotValue) {
// Вывод значения в монитор порта
Serial.print("Значение потенциометра: ");
Serial.println(potValue);
// Дополнительно: вывод в виде прогресс-бара
Serial.print("[");
int barLength = map(potValue, 0, 1023, 0, 50); // Преобразуем в шкалу от 0 до 50
for (int i = 0; i 50; i++) {
if (i barLength) {
Serial.print("="); // Заполненная часть
} else {
Serial.print(" "); // Пустая часть
}
}
Serial.print("] ");
Serial.print(map(potValue, 0, 1023, 0, 100)); // Процентное значение
Serial.println("%");
// Сохраняем текущее значение для сравнения в следующей итерации
lastPotValue = potValue;
}
// Небольшая задержка для стабильности чтения
// Без задержки значения будут считываться слишком быстро,
// что может привести к "дребезгу" значений при плавном повороте
delay(50); // 50 мс = 20 раз в секунду (достаточно для ручного управления)
}
Дополнительные сведения о потенциометрах:
- Типы потенциометров:
- Линейные (A) — сопротивление изменяется линейно с углом поворота
- Логарифмические (B) — сопротивление изменяется по логарифмическому закону (часто используются в аудиоаппаратуре)
- Обратно-логарифмические (C) — обратная логарифмическая зависимость
- Номиналы сопротивления: 1 кОм, 10 кОм, 100 кОм и другие. Для Arduino обычно используют 10 кОм.
- Подключение: важно правильно подключить выводы. Если подключить наоборот (5V и GND поменять местами), то значения будут инвертированы (1023 при повороте влево, 0 при повороте вправо).
4.2 Отправка данных с потенциометра в Python
Теория: В этом проекте мы отправляем данные с потенциометра с Arduino в Python скрипт, который визуализирует эти данные с помощью OpenCV. Это демонстрирует полный цикл: сбор данных с аналогового датчика → передача на компьютер → обработка и визуализация.
Arduino код (скетч):
/**
* Potentiometer_Serial - отправка данных с потенциометра в Python
*
* Этот скетч считывает значение с потенциометра и отправляет его
* в Python через последовательный порт с использованием библиотеки CVZone.
*
* Принцип работы:
* 1. Считывание аналогового значения с потенциометра (A0)
* 2. Упаковка значения в формат, понятный библиотеке CVZone
* 3. Отправка данных через последовательный порт
*
* Особенности библиотеки CVZone:
* - Использует собственный протокол для надежной передачи данных
* - Автоматически добавляет заголовки и контрольные суммы
* - Поддерживает передачу нескольких значений в одном пакете
*
* Формат отправляемых данных: два значения
* - Первое значение: показания потенциометра (0-1023)
* - Второе значение: не используется (резерв или может быть использовано для других данных)
*
* Компоненты:
* - Arduino Uno
* - Потенциометр 10 кОм
* - Соединительные провода
* - USB кабель для подключения к компьютеру
*/
// Подключение библиотеки CVZone для упрощенной работы с последовательным портом
#include "CVZone.h"
// Инициализация объекта SerialData для отправки данных
// Параметры конструктора: количество значений для отправки (в данном случае 2)
// Библиотека CVZone требует минимум 2 значения даже если используется только одно
SerialData serialData(2);
// Массив для хранения значений, которые будут отправляться
// Размер массива должен соответствовать параметру конструктора SerialData
int sendValues[2];
// Переменная для хранения предыдущего значения (для оптимизации отправки)
int lastSentValue = -1;
// Функция setup() выполняется один раз при запуске
void setup() {
// Инициализация последовательной связи с библиотекой CVZone
serialData.begin();
// Дополнительно: инициализация стандартного Serial для отладки
Serial.begin(9600);
Serial.println("Программа отправки данных с потенциометра запущена");
}
// Функция loop() выполняется бесконечно
void loop() {
// Чтение аналогового значения с пина A0
// analogRead() возвращает значение от 0 до 1023
int potValue = analogRead(A0);
// Отправка данных только если значение изменилось
// Это уменьшает нагрузку на последовательный порт
// и предотвращает переполнение буфера на стороне Python
if (abs(potValue - lastSentValue) > 2) { // Порог 2 для устранения "дребезга"
// Заполнение массива значениями для отправки
sendValues[0] = potValue; // Основное значение - показания потенциометра
sendValues[1] = 0; // Второе значение не используется, но обязательно должно быть
// Отправка данных через последовательный порт
// Библиотека CVZone автоматически форматирует данные
// и добавляет необходимые заголовки
serialData.send(sendValues);
// Для отладки: вывод значения в монитор порта
Serial.print("Отправлено значение: ");
Serial.println(potValue);
// Сохранение текущего значения для сравнения в следующей итерации
lastSentValue = potValue;
}
// Задержка для стабильности
// Слишком частая отправка может переполнить буфер на стороне Python
// 50 мс обеспечивает частоту обновления около 20 Гц, что достаточно для ручного управления
delay(50);
}
Python код для визуализации:
"""
Potentiometer_Graphics.py - визуализация данных с потенциометра
Этот скрипт получает данные с потенциометра через Arduino
и отображает их в виде графического интерфейса с круговой шкалой.
Особенности:
1. Получение данных от Arduino через последовательный порт
2. Визуализация в виде круговой шкалы (эллипса)
3. Отображение числового значения
4. Обработка ошибок связи
Принцип работы визуализации:
- Значение потенциометра (0-1023) преобразуется в угол (-90° до 270°)
- Рисуется дуга эллипса от начального угла (-90°) до рассчитанного угла
- Таким создается эффект круговой шкалы, заполняющейся при повороте потенциометра
Требования:
- Установленные библиотеки: cvzone, opencv-python, numpy
- Изображение potentiometer.jpg в папке resources
- Загруженный скетч Potentiometer_Serial на Arduino
"""
# Импорт необходимых библиотек
import cv2 # OpenCV для работы с изображениями и графикой
import numpy as np # NumPy для математических операций
from cvzone.SerialModule import SerialObject # Для связи с Arduino
# Инициализация связи с Arduino
print("Инициализация связи с Arduino...")
try:
# Создание объекта SerialObject для связи с Arduino
# Автоматическое определение порта (в большинстве случаев работает)
arduino = SerialObject()
# Альтернатива: указание порта вручную (если автоматическое определение не работает)
# Для Windows: обычно COM3, COM4, COM5 и т.д.
# Для Linux: обычно /dev/ttyUSB0 или /dev/ttyACM0
# Для macOS: обычно /dev/cu.usbmodem14101 или подобное
# arduino = SerialObject("COM3") # Раскомментировать и указать нужный порт
print("Связь с Arduino установлена")
except Exception as e:
print(f"Ошибка при установке связи с Arduino: {e}")
print("Проверьте:")
print("1. Подключен ли Arduino к компьютеру")
print("2. Установлены ли драйверы Arduino")
print("3. Загружен ли скетч Potentiometer_Serial на Arduino")
exit(1)
# Загрузка фонового изображения для визуализации
print("Загрузка изображения для визуализации...")
try:
# Загрузка изображения из файла
# Путь "../resources/potentiometer.jpg" означает:
# - .. - переход на уровень выше
# - resources - папка с ресурсами
# - potentiometer.jpg - файл изображения
img = cv2.imread("../resources/potentiometer.jpg")
# Проверка успешности загрузки
if img is None:
raise FileNotFoundError("Изображение не найдено")
print(f"Изображение загружено. Размер: {img.shape[1]}x{img.shape[0]}")
except Exception as e:
print(f"Ошибка при загрузке изображения: {e}")
print("Создаем изображение программно...")
# Создание изображения программно, если файл не найден
# Размер изображения: 400x400 пикселей
img = np.zeros((400, 400, 3), dtype=np.uint8)
# Заполнение фона серым цветом
img.fill(50)
# Рисование рамки
cv2.rectangle(img, (10, 10), (390, 390), (200, 200, 200), 2)
# Добавление текста
cv2.putText(img, "Potentiometer", (100, 50),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
cv2.putText(img, "Control", (150, 90),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
# Параметры для рисования круговой шкалы
# Центр круга (координаты x, y)
center = (200, 200)
# Оси эллипса (полуоси по x и y)
# Для круга оси должны быть равны
axes = (131, 131) # (ширина, высота) - одинаковые значения для круга
# Начальный угол дуги (в градусах)
# -90° соответствует положению "12 часов" на циферблате
start_angle = -90
# Переменная для хранения предыдущего значения (для оптимизации отрисовки)
last_displayed_value = -1
print("Запуск визуализации...")
print("Поворачивайте ручку потенциометра")
print("Нажмите 'q' для выхода из программы")
try:
# Основной цикл программы
while True:
# Получение данных с Arduino
# Метод getData() возвращает список значений, отправленных Arduino
data = arduino.getData()
# Проверка наличия данных
if data and len(data) > 0:
try:
# Преобразование первого элемента данных в целое число
# data[0] содержит значение потенциометра (от Arduino)
pot_value = int(data[0])
# Проверка, изменилось ли значение (для оптимизации)
if pot_value != last_displayed_value:
# Преобразование диапазона значений потенциометра (0-1023)
# в диапазон углов для эллипса (-90° до 270°)
# Это нужно потому, что функция cv2.ellipse() рисует дугу от
# start_angle до end_angle, а полный круг составляет 360°
# Начав с -90° и закончив на 270°, мы получаем полный круг 360°
angle = np.interp(pot_value, [0, 1023], [-90, 270])
# Создание копии исходного изображения для рисования
# Важно создавать копию, чтобы не портить исходное изображение
img_copy = img.copy()
# Рисование эллипса (круговой шкалы)
# Рисуем только если значение не равно 0 (полностью влево)
if pot_value != 0:
cv2.ellipse(
img_copy, # Изображение для рисования
center, # Центр эллипса (x, y)
axes, # Оси (полуоси по x и y)
0, # Угол поворота эллипса (0 - без поворота)
start_angle, # Начальный угол дуги (-90°)
angle, # Конечный угол дуги (рассчитанный)
(0, 180, 255), # Цвет в формате BGR (синий, зеленый, красный)
# (0, 180, 255) соответствует оранжевому цвету
25 # Толщина линии (в пикселях)
)
# Форматирование значения для отображения
# str(pot_value).zfill(4) добавляет ведущие нули, чтобы
# всегда было 4 цифры (например, 5 -> "0005", 42 -> "0042")
formatted_value = str(pot_value).zfill(4)
# Отображение числового значения в центре шкалы
cv2.putText(
img_copy, # Изображение для рисования
formatted_value, # Текст для отображения
(150, 220), # Позиция текста (x, y)
cv2.FONT_HERSHEY_COMPLEX_SMALL, # Шрифт
3, # Масштаб шрифта
(255, 255, 255), # Цвет текста (белый в BGR)
3 # Толщина текста
)
# Дополнительно: отображение процентного значения
# Преобразование 0-1023 в 0-100%
percent_value = int(np.interp(pot_value, [0, 1023], [0, 100]))
percent_text = f"{percent_value}%"
# Отображение процентного значения под основным
cv2.putText(
img_copy,
percent_text,
(180, 270),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
(200, 200, 200),
2
)
# Отображение изображения в окне
cv2.imshow("Potentiometer Control", img_copy)
# Сохранение текущего значения для сравнения в следующей итерации
last_displayed_value = pot_value
# Для отладки: вывод значения в консоль
# print(f"Значение потенциометра: {pot_value}")
except (ValueError, IndexError) as e:
# Обработка ошибок при некорректных данных
# ValueError: если преобразование в int не удалось
# IndexError: если data[0] не существует (пустой список)
# В реальном проекте здесь можно добавить более сложную обработку ошибок
pass
# Ожидание 1 миллисекунды для обработки событий окна
# cv2.waitKey() возвращает код нажатой клавиши
# & 0xFF - маскирование для корректной работы на всех платформах
# ord('q') - код клавиши 'q'
if cv2.waitKey(1) & 0xFF == ord('q'):
print("Выход по команде пользователя (клавиша 'q')")
break
# Обработка прерывания клавишами Ctrl+C
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем (Ctrl+C)")
# Блок finally выполняется всегда, даже если произошла ошибка
finally:
print("Завершение программы...")
# Отправка нулевого значения на Arduino (не обязательно, но полезно для завершения)
arduino.sendData([0, 0])
# Закрытие всех окон OpenCV
cv2.destroyAllWindows()
# Короткие задержки для гарантированного закрытия окон
cv2.waitKey(1)
cv2.waitKey(1)
cv2.waitKey(1)
print("Программа завершена")
Глава 5: Проекты компьютерного зрения
5.1 Базовое распознавание лиц
Теория: Распознавание лиц — одна из фундаментальных задач компьютерного зрения. В этом проекте мы используем библиотеку MediaPipe от Google, которая предоставляет современные, точные и быстрые алгоритмы для распознавания лиц, а также определения ключевых точек (landmarks).
"""
FaceDetection_Basics.py - базовое распознавание лиц с использованием MediaPipe
Этот скрипт демонстрирует использование библиотеки CVZone для распознавания лиц
с помощью MediaPipe от Google.
Преимущества MediaPipe перед классическими методами (например, Haar Cascades):
1. Более высокая точность обнаружения
2. Устойчивость к изменениям освещения, поворотам лица, частичным перекрытиям
3. Возможность определения ключевых точек лица (468 точек)
4. Работа в реальном времени даже на среднем оборудовании
Принцип работы:
1. Захват видеопотока с камеры
2. Обработка каждого кадра детектором лиц
3. Рисование ограничивающих прямоугольников вокруг обнаруженных лиц
4. Отображение результата
Требования:
- Установленные библиотеки: cvzone, opencv-python, mediapipe
- Веб-камера, подключенная к компьютеру
- Достаточное освещение для качественного распознавания
Примечание:
MediaPipe - кроссплатформенный фреймворк для построения конвейеров
машинного восприятия, разработанный Google. Он оптимизирован для работы
на различных устройствах (ПК, мобильные устройства, edge-устройства).
"""
# Импорт необходимых библиотек
import cv2 # OpenCV для работы с видео и изображениями
from cvzone.FaceDetectionModule import FaceDetector # Детектор лиц из CVZone
# Инициализация видеозахвата
# Параметр 0 означает использование первой доступной камеры
# Если у вас несколько камер, можно попробовать 1, 2 и т.д.
cap = cv2.VideoCapture(1)
# Проверка успешности открытия камеры
if not cap.isOpened():
print("Ошибка: не удалось открыть камеру")
print("Проверьте:")
print("1. Подключена ли камера к компьютеру")
print("2. Не используется ли камера другой программой")
print("3. Правильность указания индекса камеры")
exit(1)
# Получение информации о камере (для отладки)
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
print(f"Камера успешно инициализирована")
print(f"Разрешение: {frame_width}x{frame_height}")
print(f"Частота кадров: {fps if fps > 0 else 'неизвестно'}")
# Инициализация детектора лиц
# Параметры:
# minDetectionCon - минимальная уверенность для детекции (0.0-1.0)
# Чем выше значение, тем более "уверенным" должен быть детектор
# чтобы считать обнаружение лицом. Рекомендуется 0.5-0.7
detector = FaceDetector(minDetectionCon=0.5)
# Счетчик кадров для расчета FPS (кадров в секунду)
frame_count = 0
start_time = cv2.getTickCount()
print("\nЗапуск распознавания лиц...")
print("Нажмите 'q' для выхода")
print("Нажмите 's' для сохранения текущего кадра")
print("Нажмите 'd' для переключения отображения bounding box")
# Переменные для управления отображением
show_bbox = True # Флаг для отображения ограничивающих прямоугольников
try:
# Основной цикл обработки видео
while True:
# Чтение кадра с камеры
# success - булевский флаг (True если кадр прочитан успешно)
# img - сам кадр в виде массива NumPy
success, img = cap.read()
# Проверка успешности чтения кадра
if not success:
print("Ошибка: не удалось прочитать кадр с камеры")
break
# Увеличение счетчика кадров
frame_count += 1
# Поиск лиц на изображении
# Метод findFaces() возвращает:
# - img: изображение с нарисованными ограничивающими прямоугольниками (если draw=True)
# - bboxs: список ограничивающих прямоугольников для обнаруженных лиц
# Каждый прямоугольник представлен как [x, y, width, height, confidence]
# где (x, y) - координаты левого верхнего угла
img, bboxs = detector.findFaces(img, draw=show_bbox)
# Вывод информации о количестве обнаруженных лиц
if len(bboxs) > 0:
# Для каждого обнаруженного лица
for i, bbox in enumerate(bboxs):
# Извлечение информации из bounding box
x, y, w, h = bbox['bbox'][:4] # Координаты и размеры
confidence = bbox['bbox'][4] # Уверенность детекции
# Вывод информации в консоль (только для первого лица, чтобы не засорять вывод)
if i == 0:
print(f"Обнаружено лицо {i+1}: x={x}, y={y}, w={w}, h={h}, уверенность={confidence:.2f}")
# Расчет и отображение FPS (кадров в секунду)
current_time = cv2.getTickCount()
elapsed_time = (current_time - start_time) / cv2.getTickFrequency()
if elapsed_time >= 1.0: # Каждую секунду
fps_calculated = frame_count / elapsed_time
print(f"FPS: {fps_calculated:.1f}")
frame_count = 0
start_time = current_time
# Отображение FPS на изображении
cv2.putText(img, f"FPS: {fps_calculated:.1f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# Отображение количества обнаруженных лиц
cv2.putText(img, f"Лиц: {len(bboxs)}", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# Отображение изображения с разметкой
cv2.imshow("Face Detection", img)
# Обработка нажатий клавиш
key = cv2.waitKey(1) & 0xFF
if key == ord('q'): # Выход при нажатии 'q'
print("Выход по команде пользователя (клавиша 'q')")
break
elif key == ord('s'): # Сохранение кадра при нажатии 's'
filename = f"face_detection_{cv2.getTickCount()}.jpg"
cv2.imwrite(filename, img)
print(f"Кадр сохранен как {filename}")
elif key == ord('d'): # Переключение отображения bounding box
show_bbox = not show_bbox
print(f"Отображение bounding box: {'ВКЛ' if show_bbox else 'ВЫКЛ'}")
# Обработка прерывания клавишами Ctrl+C
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем (Ctrl+C)")
# Блок finally выполняется всегда, даже если произошла ошибка
finally:
print("Завершение программы...")
# Освобождение ресурсов камеры
cap.release()
print("Ресурсы камеры освобождены")
# Закрытие всех окон OpenCV
cv2.destroyAllWindows()
# Короткие задержки для гарантированного закрытия окон
cv2.waitKey(1)
cv2.waitKey(1)
cv2.waitKey(1)
print("Программа завершена")
Расширенная версия с дополнительными возможностями:
"""
FaceDetection_Advanced.py - расширенное распознавание лиц с дополнительными функциями
Дополнительные возможности:
1. Отслеживание идентификаторов лиц (если лицо появляется снова)
2. Определение расстояния до лица (приблизительное)
3. Определение эмоций (базовое, по положению ключевых точек)
4. Запись видео с обнаруженными лицами
"""
import cv2
import numpy as np
from cvzone.FaceDetectionModule import FaceDetector
import time
# Инициализация
cap = cv2.VideoCapture(1)
detector = FaceDetector(minDetectionCon=0.5)
# Настройки записи видео
record_video = False
video_writer = None
# Словарь для отслеживания лиц (ID -> время последнего обнаружения)
tracked_faces = {}
next_face_id = 0
FACE_TIMEOUT = 2.0 # Секунды до удаления лица из трекинга
# Калибровка для определения расстояния
# Предполагаемая ширина лица в реальном мире (в см)
REAL_FACE_WIDTH = 15.0 # Средняя ширина лица взрослого человека
FOCAL_LENGTH = 500 # Примерное фокусное расстояние камеры
def calculate_distance(face_width_pixels):
"""Вычисление приблизительного расстояния до лица"""
if face_width_pixels == 0:
return 0
distance = (REAL_FACE_WIDTH * FOCAL_LENGTH) / face_width_pixels
return distance
def assign_face_id(bbox, tracked_faces, next_id):
"""Назначение или обновление ID для лица"""
x, y, w, h = bbox['bbox'][:4]
center_x, center_y = x + w//2, y + h//2
current_time = time.time()
# Поиск ближайшего известного лица
min_distance = float('inf')
assigned_id = -1
for face_id, face_data in tracked_faces.items():
last_x, last_y, last_time = face_data
# Проверка времени (если лицо давно не видели, считаем новым)
if current_time - last_time > FACE_TIMEOUT:
continue
# Вычисление расстояния между центрами
distance = np.sqrt((center_x - last_x)**2 + (center_y - last_y)**2)
# Если расстояние меньше порога и это ближайшее лицо
if distance 100 and distance min_distance: # Порог 100 пикселей
min_distance = distance
assigned_id = face_id
# Если близкое лицо не найдено, назначаем новый ID
if assigned_id == -1:
assigned_id = next_id
next_id += 1
# Обновление данных лица
tracked_faces[assigned_id] = (center_x, center_y, current_time)
return assigned_id, next_id
print("Запуск расширенного распознавания лиц...")
print("Команды:")
print(" q - выход")
print(" r - начать/остановить запись видео")
print(" c - очистить историю трекинга")
try:
while True:
success, img = cap.read()
if not success:
break
# Обнаружение лиц
img, bboxs = detector.findFaces(img, draw=True)
# Обработка каждого обнаруженного лица
for bbox in bboxs:
# Назначение/обновление ID
face_id, next_face_id = assign_face_id(bbox, tracked_faces, next_face_id)
# Извлечение информации
x, y, w, h = bbox['bbox'][:4]
confidence = bbox['bbox'][4]
# Вычисление расстояния
distance = calculate_distance(w)
# Отображение информации
info_text = f"ID: {face_id}, Dist: {distance:.1f}cm"
cv2.putText(img, info_text, (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# Рисование ID в центре лица
cv2.putText(img, str(face_id), (x + w//2 - 10, y + h//2),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
# Очистка устаревших лиц из трекинга
current_time = time.time()
faces_to_remove = []
for face_id, face_data in tracked_faces.items():
last_time = face_data[2]
if current_time - last_time > FACE_TIMEOUT:
faces_to_remove.append(face_id)
for face_id in faces_to_remove:
del tracked_faces[face_id]
print(f"Лицо ID {face_id} удалено из трекинга")
# Отображение статистики
cv2.putText(img, f"Отслеживаемые лица: {len(tracked_faces)}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# Запись видео (если включена)
if record_video:
if video_writer is None:
# Создание VideoWriter при первом включении записи
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
if fps == 0:
fps = 30
fourcc = cv2.VideoWriter_fourcc(*'XVID')
video_writer = cv2.VideoWriter('face_detection_output.avi',
fourcc, fps, (frame_width, frame_height))
print("Начата запись видео...")
video_writer.write(img)
cv2.putText(img, "REC", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
cv2.imshow("Advanced Face Detection", img)
# Обработка клавиш
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('r'):
record_video = not record_video
if not record_video and video_writer is not None:
video_writer.release()
video_writer = None
print("Запись видео остановлена")
elif key == ord('c'):
tracked_faces.clear()
next_face_id = 0
print("История трекинга очищена")
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем")
finally:
cap.release()
if video_writer is not None:
video_writer.release()
cv2.destroyAllWindows()
5.2 Распознавание лиц с управлением LED
Теория: Этот проект объединяет компьютерное зрение и управление физическими устройствами. Когда система обнаруживает лицо в кадре, она включает светодиод на Arduino. Это простой, но наглядный пример системы автоматизации на основе компьютерного зрения.
Arduino код (скетч):
/**
* FaceDetection_LED.ino - управление LED на основе распознавания лиц
*
* Этот скетч управляет светодиодом в зависимости от команд, полученных от Python.
* Когда Python обнаруживает лицо, он отправляет команду 1 (включить LED).
* Когда лицо не обнаружено, отправляется команда 0 (выключить LED).
*
* Принцип работы:
* 1. Ожидание команд от Python через последовательный порт
* 2. Получение команды (0 или 1) в массиве receivedValues[0]
* 3. Управление светодиодом на основе полученной команды
*
* Компоненты:
* - Arduino Uno
* - Светодиод (или использование встроенного на пине 13)
* - Резистор 220 Ом (если используется внешний светодиод)
*
* Подключение светодиода:
* - Анод (+) через резистор 220 Ом к пину 13
* - Катод (-) к GND
*
* Примечание: для простоты можно использовать встроенный светодиод на пине 13
*/
// Подключение библиотеки CVZone для упрощенной работы с последовательным портом
#include "CVZone.h"
// Инициализация объекта SerialData для приема данных
// Параметры: количество значений (1) и количество цифр в каждом значении (1)
SerialData serialData(1, 1);
// Массив для хранения полученных значений
// receivedValues[0] будет содержать команду (0 или 1)
int receivedValues[1];
// Пин для подключения светодиода
const int LED_PIN = 13;
// Переменная для хранения текущего состояния светодиода
// Используется для оптимизации (не обновлять состояние, если не изменилось)
int currentLedState = LOW;
// Функция setup() выполняется один раз при запуске
void setup() {
// Настройка пина светодиода как выход
pinMode(LED_PIN, OUTPUT);
// Установка начального состояния (выключено)
digitalWrite(LED_PIN, LOW);
currentLedState = LOW;
// Инициализация последовательной связи с библиотекой CVZone
serialData.begin();
// Дополнительно: инициализация стандартного Serial для отладки
Serial.begin(9600);
Serial.println("Система управления LED на основе распознавания лиц");
Serial.println("Ожидание команд от Python...");
Serial.println("0 - лицо не обнаружено (LED выключен)");
Serial.println("1 - лицо обнаружено (LED включен)");
}
// Функция loop() выполняется бесконечно
void loop() {
// Получение данных из последовательного порта
// Библиотека CVZone автоматически парсит входящие данные
// и заполняет массив receivedValues
serialData.getData(receivedValues);
// Определение команды из полученных данных
// receivedValues[0] содержит команду от Python
int command = receivedValues[0];
// Определение желаемого состояния светодиода на основе команды
int desiredLedState;
if (command == 1) {
desiredLedState = HIGH; // Включить LED
} else {
desiredLedState = LOW; // Выключить LED
}
// Изменение состояния светодиода только если оно изменилось
// Это предотвращает мигание светодиода при частых одинаковых командах
if (desiredLedState != currentLedState) {
digitalWrite(LED_PIN, desiredLedState);
currentLedState = desiredLedState;
// Вывод состояния в монитор порта для отладки
Serial.print("Состояние LED изменено: ");
Serial.println(desiredLedState == HIGH ? "ВКЛ" : "ВЫКЛ");
}
// Небольшая задержка для стабильности
// Слишком быстрый цикл может привести к высокой загрузке процессора
delay(50); // 50 мс обеспечивает частоту обновления 20 Гц
}
Python код:
"""
FaceDetection_LED.py - распознавание лиц с управлением LED на Arduino
Этот проект объединяет компьютерное зрение и управление оборудованием:
- При обнаружении лица включается LED на Arduino
- При отсутствии лица LED выключается
Принцип работы:
1. Захват видеопотока с веб-камеры
2. Обнаружение лиц на каждом кадре с помощью MediaPipe
3. Отправка команды на Arduino при изменении состояния (обнаружено/не обнаружено)
4. Визуализация результата с отображением состояния LED
Особенности реализации:
- Используется пороговое значение уверенности обнаружения (detectionCon)
- Реализована защита от "дребезга" (частой смены состояний)
- Оптимизирована отправка команд (только при изменении состояния)
Требования:
- Установленные библиотеки: cvzone, opencv-python, mediapipe
- Arduino с загруженным скетчем FaceDetection_LED.ino
- Веб-камера
"""
# Импорт необходимых библиотек
import cv2
from cvzone.FaceDetectionModule import FaceDetector
from cvzone.SerialModule import SerialObject
import time
# Инициализация видеозахвата
# Используем камеру с индексом 1 (обычно внешняя камера)
# Если не работает, попробуйте индекс 0
cap = cv2.VideoCapture(1)
# Проверка успешности открытия камеры
if not cap.isOpened():
print("Ошибка: не удалось открыть камеру")
# Попробуем другие индексы камер
for i in range(3):
cap = cv2.VideoCapture(i)
if cap.isOpened():
print(f"Камера найдена на индексе {i}")
break
else:
print("Камера не найдена. Проверьте подключение.")
exit(1)
# Инициализация детектора лиц
# minDetectionCon - минимальная уверенность для детекции
# Рекомендуемые значения: 0.5-0.7
# Более высокие значения уменьшают ложные срабатывания,
# но могут пропускать лица при плохом освещении или под углом
detector = FaceDetector(minDetectionCon=0.6)
# Инициализация связи с Arduino
print("Подключение к Arduino...")
try:
arduino = SerialObject()
print("Соединение с Arduino установлено")
except Exception as e:
print(f"Ошибка подключения к Arduino: {e}")
print("Продолжаем без Arduino (только визуализация)")
arduino = None
# Переменные для управления состоянием
face_detected = False # Текущее состояние (обнаружено ли лицо)
last_state_change = time.time() # Время последнего изменения состояния
state_change_delay = 0.5 # Минимальная задержка между изменениями состояния (в секундах)
# Переменные для расчета FPS
frame_count = 0
start_time = time.time()
print("\nСистема распознавания лиц с управлением LED запущена")
print("Направьте лицо в камеру для включения LED")
print("Нажмите 'q' для выхода")
print("Нажмите 's' для сохранения статистики")
try:
# Основной цикл обработки видео
while True:
# Чтение кадра с камеры
success, img = cap.read()
if not success:
print("Ошибка чтения кадра с камеры")
break
frame_count += 1
# Обнаружение лиц на кадре
# detector.findFaces() возвращает:
# - img: изображение с нарисованными bounding boxes
# - bboxs: список обнаруженных лиц с их координатами и уверенностью
img, bboxs = detector.findFaces(img, draw=True)
# Определение текущего состояния
# Лицо считается обнаруженным, если найдено хотя бы одно лицо
current_face_detected = len(bboxs) > 0
# Проверка, изменилось ли состояние
current_time = time.time()
if current_face_detected != face_detected:
# Проверка задержки (чтобы избежать "дребезга")
if current_time - last_state_change >= state_change_delay:
# Обновление состояния
face_detected = current_face_detected
last_state_change = current_time
# Отправка команды на Arduino (если подключен)
if arduino is not None:
if face_detected:
arduino.sendData([1]) # Включить LED
print("Лицо обнаружено -> LED ВКЛ")
else:
arduino.sendData([0]) # Выключить LED
print("Лицо не обнаружено -> LED ВЫКЛ")
# Визуализация состояния
# Определение цвета и текста в зависимости от состояния
if face_detected:
status_color = (0, 255, 0) # Зеленый
status_text = "FACE DETECTED - LED ON"
led_status = "ON"
led_color = (0, 255, 0)
else:
status_color = (0, 0, 255) # Красный
status_text = "NO FACE - LED OFF"
led_status = "OFF"
led_color = (0, 0, 255)
# Рисование панели состояния в верхней части изображения
cv2.rectangle(img, (0, 0), (img.shape[1], 70), (40, 40, 40), -1)
# Отображение текста состояния
cv2.putText(img, status_text, (20, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, status_color, 2)
# Отображение количества обнаруженных лиц
cv2.putText(img, f"Лиц: {len(bboxs)}", (20, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
# Визуализация "светодиода" на изображении
led_center = (img.shape[1] - 50, 35)
led_radius = 20
# Рисование светодиода (круга)
cv2.circle(img, led_center, led_radius, led_color, -1)
# Добавление блика для реалистичности
cv2.circle(img, (led_center[0] - 5, led_center[1] - 5),
5, (255, 255, 255), -1)
# Подпись "LED"
cv2.putText(img, "LED", (img.shape[1] - 70, 70),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
# Отображение статуса LED
cv2.putText(img, led_status, (img.shape[1] - 60, 90),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, led_color, 1)
# Расчет и отображение FPS
elapsed_time = time.time() - start_time
if elapsed_time >= 1.0:
fps = frame_count / elapsed_time
frame_count = 0
start_time = time.time()
# Отображение FPS
cv2.putText(img, f"FPS: {fps:.1f}", (img.shape[1] - 100, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# Отображение изображения
cv2.imshow("Face Detection with LED Control", img)
# Обработка нажатий клавиш
key = cv2.waitKey(1) & 0xFF
if key == ord('q'): # Выход
print("Выход по команде пользователя")
break
elif key == ord('s'): # Сохранение статистики
with open("face_detection_stats.txt", "w") as f:
f.write(f"Общее время работы: {time.time() - start_time:.1f} сек\n")
f.write(f"Средний FPS: {fps:.1f}\n")
f.write(f"Состояние LED: {led_status}\n")
f.write(f"Обнаружено лиц: {len(bboxs)}\n")
print("Статистика сохранена в face_detection_stats.txt")
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем (Ctrl+C)")
finally:
print("\nЗавершение программы...")
# Отправка команды на выключение LED (если Arduino подключен)
if arduino is not None:
arduino.sendData([0])
print("LED выключен")
# Освобождение ресурсов камеры
cap.release()
print("Ресурсы камеры освобождены")
# Закрытие окон OpenCV
cv2.destroyAllWindows()
# Короткие задержки для гарантированного закрытия окон
cv2.waitKey(1)
cv2.waitKey(1)
cv2.waitKey(1)
print("Программа завершена")
5.3 Распознавание лиц с управлением RGB LED
Теория: В этом проекте мы расширяем функциональность, используя RGB светодиод вместо обычного. RGB LED может отображать разные цвета, что позволяет визуализировать различные состояния системы (например, зеленый — лицо обнаружено, красный — лицо не обнаружено, синий — обработка и т.д.).
Arduino код для управления RGB LED:
/**
* RGB_LED_FaceDetection.ino - управление RGB LED на основе распознавания лиц
*
* Этот скетч управляет RGB светодиодом в зависимости от команд, полученных от Python.
* Ожидается получение 3 значений (R, G, B), где:
* - 0 = включить соответствующий цвет
* - 1 = выключить соответствующий цвет
*
* Важно: данная схема управления использует общий катод (common cathode).
* Если у вас RGB LED с общим анодом (common anode), логика будет обратной.
*
* Подключение RGB LED с общим катодом:
* - R (красный) -> пин 8 через резистор 220 Ом
* - G (зеленый) -> пин 9 через резистор 220 Ом
* - B (синий) -> пин 10 через резистор 220 Ом
* - Катод (общий, минус) -> GND
*
* Подключение RGB LED с общим анодом:
* - R (красный) -> пин 8 через резистор 220 Ом
* - G (зеленый) -> пин 9 через резистор 220 Ом
* - B (синий) -> пин 10 через резистор 220 Ом
* - Анод (общий, плюс) -> 5V
* - В коде нужно инвертировать логику (HIGH/LOW меняются местами)
*
* Компоненты:
* - Arduino Uno
* - RGB светодиод (общий катод рекомендуется)
* - 3 резистора 220 Ом
* - Соединительные провода
*/
// Подключение библиотеки CVZone
#include "CVZone.h"
// Определение пинов для RGB LED
// Измените эти номера, если используете другие пины
const int RED_PIN = 8;
const int GREEN_PIN = 9;
const int BLUE_PIN = 10;
// Инициализация объекта SerialData для приема данных
// Ожидаем 3 значения (R, G, B), каждое по 1 цифре
SerialData serialData(3, 1);
// Массив для хранения полученных значений
// receivedValues[0] - состояние красного цвета
// receivedValues[1] - состояние зеленого цвета
// receivedValues[2] - состояние синего цвета
int receivedValues[3];
// Переменные для хранения текущего состояния цветов
// Используются для оптимизации (не обновлять, если не изменилось)
int currentRedState = HIGH;
int currentGreenState = HIGH;
int currentBlueState = HIGH;
// Функция setup() выполняется один раз при запуске
void setup() {
// Настройка пинов RGB LED как выходов
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
// Установка начального состояния (все цвета выключены)
// Для RGB LED с общим катодом:
// HIGH = выключить (нет напряжения)
// LOW = включить (0V, ток течет от пина к катоду)
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, HIGH);
// Сохранение начального состояния
currentRedState = HIGH;
currentGreenState = HIGH;
currentBlueState = HIGH;
// Инициализация последовательной связи
serialData.begin();
// Дополнительно: инициализация стандартного Serial для отладки
Serial.begin(9600);
Serial.println("Система управления RGB LED на основе распознавания лиц");
Serial.println("Ожидание команд от Python...");
Serial.println("Формат команд: R,G,B (0=включить, 1=выключить)");
Serial.println("Примеры:");
Serial.println(" 0,1,1 - включить красный (зеленый и синий выключены)");
Serial.println(" 1,0,1 - включить зеленый (красный и синий выключены)");
Serial.println(" 1,1,0 - включить синий (красный и зеленый выключены)");
Serial.println(" 0,0,1 - включить красный и зеленый (желтый цвет)");
}
// Функция loop() выполняется бесконечно
void loop() {
// Получение данных из последовательного порта
serialData.getData(receivedValues);
// Извлечение команд для каждого цвета
// Полученные значения: 0 = включить цвет, 1 = выключить цвет
// Но нам нужно преобразовать в уровни сигнала:
// Для общего катода: 0 -> LOW (включить), 1 -> HIGH (выключить)
int redCommand = receivedValues[0];
int greenCommand = receivedValues[1];
int blueCommand = receivedValues[2];
// Преобразование команд в уровни сигнала
int desiredRedState = (redCommand == 0) ? LOW : HIGH;
int desiredGreenState = (greenCommand == 0) ? LOW : HIGH;
int desiredBlueState = (blueCommand == 0) ? LOW : HIGH;
// Обновление состояния красного цвета, если оно изменилось
if (desiredRedState != currentRedState) {
digitalWrite(RED_PIN, desiredRedState);
currentRedState = desiredRedState;
// Вывод состояния для отладки
Serial.print("Красный: ");
Serial.println(desiredRedState == LOW ? "ВКЛ" : "ВЫКЛ");
}
// Обновление состояния зеленого цвета, если оно изменилось
if (desiredGreenState != currentGreenState) {
digitalWrite(GREEN_PIN, desiredGreenState);
currentGreenState = desiredGreenState;
// Вывод состояния для отладки
Serial.print("Зеленый: ");
Serial.println(desiredGreenState == LOW ? "ВКЛ" : "ВЫКЛ");
}
// Обновление состояния синего цвета, если оно изменилось
if (desiredBlueState != currentBlueState) {
digitalWrite(BLUE_PIN, desiredBlueState);
currentBlueState = desiredBlueState;
// Вывод состояния для отладки
Serial.print("Синий: ");
Serial.println(desiredBlueState == LOW ? "ВКЛ" : "ВЫКЛ");
}
// Небольшая задержка для стабильности
delay(50);
}
// Дополнительная функция для управления цветами по имени
// Может быть использована для расширения функциональности
void setColorByName(String colorName) {
if (colorName == "RED") {
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, HIGH);
} else if (colorName == "GREEN") {
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, HIGH);
} else if (colorName == "BLUE") {
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, LOW);
} else if (colorName == "YELLOW") {
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, HIGH);
} else if (colorName == "MAGENTA") {
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, LOW);
} else if (colorName == "CYAN") {
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
} else if (colorName == "WHITE") {
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
} else { // Выключить все
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, HIGH);
}
}
Python код для управления RGB LED:
"""
FaceDetection_RGB.py - распознавание лиц с управлением RGB LED
Этот проект расширяет базовое распознавание лиц, добавляя управление
RGB LED для визуальной индикации различных состояний:
Состояния и соответствующие цвета:
- Зеленый: лицо обнаружено
- Красный: лицо не обнаружено
- Синий: обработка/поиск лиц
- Желтый: низкая уверенность обнаружения
- Пурпурный: обнаружено несколько лиц
Особенности:
1. Плавные переходы между цветами
2. Настраиваемые параметры чувствительности
3. Визуализация состояния RGB LED на экране
4. Возможность калибровки цветов под конкретный RGB LED
Требования:
- Установленные библиотеки: cvzone, opencv-python, mediapipe, numpy
- Arduino с загруженным скетчем RGB_LED_FaceDetection.ino
- RGB светодиод, подключенный к Arduino
- Веб-камера
"""
import cv2
import numpy as np
from cvzone.FaceDetectionModule import FaceDetector
from cvzone.SerialModule import SerialObject
import time
# Инициализация видеозахвата
cap = cv2.VideoCapture(1)
# Проверка камеры
if not cap.isOpened():
print("Ошибка: не удалось открыть камеру")
# Попробуем найти камеру
for i in range(3):
cap = cv2.VideoCapture(i)
if cap.isOpened():
print(f"Камера найдена на индексе {i}")
break
else:
print("Камера не найдена")
exit(1)
# Инициализация детектора лиц
# Увеличиваем минимальную уверенность для более надежного обнаружения
detector = FaceDetector(minDetectionCon=0.7)
# Инициализация связи с Arduino
print("Подключение к Arduino...")
try:
arduino = SerialObject()
print("Соединение с Arduino установлено")
except Exception as e:
print(f"Ошибка подключения к Arduino: {e}")
print("Работаем в режиме симуляции (без реального LED)")
arduino = None
# Определение цветов для различных состояний
# Формат: (R, G, B) где 0=включить, 1=выключить
# Для RGB LED с общим катодом (common cathode)
COLORS = {
'RED': [0, 1, 1], # Только красный
'GREEN': [1, 0, 1], # Только зеленый
'BLUE': [1, 1, 0], # Только синий
'YELLOW': [0, 0, 1], # Красный + зеленый
'MAGENTA':[0, 1, 0], # Красный + синий
'CYAN': [1, 0, 0], # Зеленый + синий
'WHITE': [0, 0, 0], # Все цвета
'OFF': [1, 1, 1] # Все цвета выключены
}
# Текущее состояние системы
current_state = 'BLUE' # Начинаем с синего (поиск)
last_state_change = time.time()
# Счетчик кадров для статистики
frame_count = 0
faces_detected_total = 0
start_time = time.time()
print("\nСистема распознавания лиц с RGB LED запущена")
print("Цвета индикации:")
print(" Зеленый - лицо обнаружено")
print(" Красный - лицо не обнаружено")
print(" Синий - обработка/поиск")
print(" Желтый - низкая уверенность")
print(" Пурпурный - несколько лиц")
print("\nНажмите 'q' для выхода")
print("Нажмите 'c' для калибровки цветов")
def calculate_face_confidence(bboxs):
"""Вычисление средней уверенности обнаружения лиц"""
if not bboxs:
return 0.0
total_confidence = 0.0
for bbox in bboxs:
if 'bbox' in bbox and len(bbox['bbox']) > 4:
total_confidence += bbox['bbox'][4]
return total_confidence / len(bboxs)
try:
# Основной цикл обработки
while True:
success, img = cap.read()
if not success:
print("Ошибка чтения кадра")
break
frame_count += 1
# Обнаружение лиц
img, bboxs = detector.findFaces(img, draw=True)
# Определение нового состояния на основе обнаруженных лиц
num_faces = len(bboxs)
if num_faces > 0:
faces_detected_total += num_faces
# Вычисление средней уверенности
avg_confidence = calculate_face_confidence(bboxs)
# Определение состояния на основе количества лиц и уверенности
if num_faces == 1:
if avg_confidence >= 0.8:
new_state = 'GREEN' # Высокая уверенность, одно лицо
elif avg_confidence >= 0.6:
new_state = 'YELLOW' # Средняя уверенность
else:
new_state = 'BLUE' # Низкая уверенность, продолжать поиск
else:
new_state = 'MAGENTA' # Несколько лиц
else:
new_state = 'RED' # Лица не обнаружены
# Проверка изменения состояния
if new_state != current_state:
# Добавляем задержку для предотвращения "дребезга"
if time.time() - last_state_change > 0.3:
current_state = new_state
last_state_change = time.time()
# Отправка команды на Arduino
if arduino is not None:
arduino.sendData(COLORS[current_state])
print(f"Состояние изменено: {current_state}")
# Визуализация на изображении
# Создаем информационную панель
info_panel = np.zeros((100, img.shape[1], 3), dtype=np.uint8)
# Цвет информационной панели соответствует состоянию
state_colors_bgr = {
'RED': (0, 0, 255),
'GREEN': (0, 255, 0),
'BLUE': (255, 0, 0),
'YELLOW': (0, 255, 255),
'MAGENTA': (255, 0, 255),
'CYAN': (255, 255, 0),
'WHITE': (255, 255, 255),
'OFF': (100, 100, 100)
}
panel_color = state_colors_bgr.get(current_state, (100, 100, 100))
info_panel[:] = panel_color
# Добавление текста на панель
state_names = {
'RED': "ЛИЦО НЕ ОБНАРУЖЕНО",
'GREEN': "ЛИЦО ОБНАРУЖЕНО",
'BLUE': "ПОИСК ЛИЦ",
'YELLOW': "НИЗКАЯ УВЕРЕННОСТЬ",
'MAGENTA': "НЕСКОЛЬКО ЛИЦ",
'CYAN': "СИСТЕМА",
'WHITE': "ВСЕ ЦВЕТА",
'OFF': "СИСТЕМА ВЫКЛЮЧЕНА"
}
state_text = state_names.get(current_state, "НЕИЗВЕСТНО")
cv2.putText(info_panel, state_text, (20, 40),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
# Отображение количества лиц
cv2.putText(info_panel, f"Лиц: {num_faces}", (img.shape[1] - 150, 40),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
# Объединение информационной панели с основным изображением
img_with_panel = np.vstack([info_panel, img])
# Визуализация RGB LED на изображении
led_size = 60
led_x = img.shape[1] - led_size - 20
led_y = 20
# Создаем изображение RGB LED
led_display = np.zeros((led_size, led_size, 3), dtype=np.uint8)
# Устанавливаем цвет LED в соответствии с состоянием
if current_state == 'RED':
led_display[:] = (0, 0, 255) # Красный в BGR
elif current_state == 'GREEN':
led_display[:] = (0, 255, 0) # Зеленый в BGR
elif current_state == 'BLUE':
led_display[:] = (255, 0, 0) # Синий в BGR
elif current_state == 'YELLOW':
led_display[:] = (0, 255, 255) # Желтый в BGR
elif current_state == 'MAGENTA':
led_display[:] = (255, 0, 255) # Пурпурный в BGR
elif current_state == 'CYAN':
led_display[:] = (255, 255, 0) # Голубой в BGR
elif current_state == 'WHITE':
led_display[:] = (255, 255, 255) # Белый в BGR
else:
led_display[:] = (50, 50, 50) # Серый (выключен)
# Добавляем блик для реалистичности
cv2.circle(led_display, (led_size//3, led_size//3),
led_size//6, (255, 255, 255), -1)
# Добавляем обводку
cv2.rectangle(led_display, (0, 0), (led_size-1, led_size-1),
(200, 200, 200), 2)
# Размещаем LED на информационной панели
img_with_panel[led_y:led_y+led_size, led_x:led_x+led_size] = led_display
# Добавляем подпись
cv2.putText(img_with_panel, "RGB LED",
(led_x, led_y + led_size + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
# Расчет и отображение FPS
elapsed_time = time.time() - start_time
if elapsed_time >= 1.0:
fps = frame_count / elapsed_time
frame_count = 0
start_time = time.time()
# Отображение FPS
cv2.putText(img_with_panel, f"FPS: {fps:.1f}",
(20, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# Отображение среднего количества лиц в секунду
faces_per_second = faces_detected_total / (time.time() - start_time + 1)
cv2.putText(img_with_panel, f"Лиц/сек: {faces_per_second:.1f}",
(20, 105), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# Отображение изображения
cv2.imshow("Face Detection with RGB LED", img_with_panel)
# Обработка клавиш
key = cv2.waitKey(1) & 0xFF
if key == ord('q'): # Выход
print("Выход по команде пользователя")
break
elif key == ord('c'): # Калибровка цветов
print("\nКалибровка цветов RGB LED")
print("Последовательно включаются все цвета")
if arduino is not None:
for color_name, color_values in COLORS.items():
if color_name != 'OFF':
print(f"Включение: {color_name}")
arduino.sendData(color_values)
time.sleep(1)
# Возврат к текущему состоянию
arduino.sendData(COLORS[current_state])
print("Калибровка завершена")
elif key == ord('t'): # Тест всех цветов
print("Тестирование всех цветов...")
if arduino is not None:
test_colors = ['RED', 'GREEN', 'BLUE', 'YELLOW', 'MAGENTA', 'CYAN', 'WHITE']
for color in test_colors:
arduino.sendData(COLORS[color])
time.sleep(0.5)
arduino.sendData(COLORS[current_state])
except KeyboardInterrupt:
print("\nПрограмма остановлена пользователем")
finally:
print("\nЗавершение программы...")
# Выключение RGB LED
if arduino is not None:
arduino.sendData(COLORS['OFF'])
print("RGB LED выключен")
# Освобождение ресурсов
cap.release()
cv2.destroyAllWindows()
# Вывод статистики
total_time = time.time() - start_time
print(f"\nСтатистика работы:")
print(f" Общее время: {total_time:.1f} секунд")
print(f" Всего кадров: {frame_count}")
print(f" Всего обнаружено лиц: {faces_detected_total}")
if total_time > 0:
print(f" Средний FPS: {frame_count/total_time:.1f}")
print("Программа завершена")
Глава 6: Практические рекомендации и советы
6.1 Организация проектов
Правильная организация файлов и папок критически важна для поддержания порядка в проектах, особенно когда они становятся сложными. Вот рекомендуемая структура, которую мы использовали в курсе:
project_folder/ # Корневая папка проекта
│
├── Arduino_code/ # Папка со скетчами Arduino
│ ├── inputs/ # Скетчи для устройств ввода
│ │ ├── potentiometer_basic.ino
│ │ └── potentiometer_serial.ino
│ │
│ └── outputs/ # Скетчи для устройств вывода
│ ├── led_basic.ino
│ ├── led_serial.ino
│ └── rgb_led.ino
│
├── Python_code/ # Папка со скриптами Python
│ ├── inputs/ # Скрипты для обработки ввода
│ │ ├── potentiometer_graphics.py
│ │ └── ...
│ │
│ └── outputs/ # Скрипты для управления выводом
│ ├── led_control.py
│ ├── led_graphics.py
│ ├── face_detection_basics.py
│ ├── face_detection_led.py
│ └── face_detection_rgb.py
│
├── resources/ # Папка с ресурсами
│ ├── images/ # Изображения
│ │ ├── LED_on.jpg
│ │ ├── LED_off.jpg
│ │ └── potentiometer.jpg
│ │
│ ├── videos/ # Видеофайлы (если нужны)
│ │
│ └── models/ # Модели машинного обучения (если нужны)
│
├── docs/ # Документация
│ ├── schematics/ # Электрические схемы
│ ├── diagrams/ # Блок-схемы алгоритмов
│ └── README.md # Основная документация проекта
│
├── tests/ # Тесты
│ ├── unit_tests/ # Модульные тесты
│ └── integration_tests/ # Интеграционные тесты
│
└── README.md # Корневой README файл проекта
Подробное описание структуры:
- Arduino_code/ — содержит все скетчи Arduino:
- Разделение на
inputs/иoutputs/помогает организовать код по функциональности - Каждый файл должен иметь понятное имя, отражающее его назначение
- Внутри файлов используйте комментарии для разделения логических блоков
- Разделение на
- Python_code/ — содержит все скрипты Python:
- Аналогичное разделение на
inputs/иoutputs/ - Для сложных проектов можно добавить подпапки по темам (face_detection/, object_tracking/ и т.д.)
- Каждый скрипт должен начинаться с комментария, описывающего его назначение
- Аналогичное разделение на
- resources/ — содержит все необходимые ресурсы:
- Изображения, видео, модели машинного обучения
- Использование относительных путей (
../resources/) делает проект переносимым - Храните здесь только необходимые файлы, чтобы не захламлять проект
- docs/ — документация проекта:
- Электрические схемы в формате PDF или изображениях
- Блок-схемы алгоритмов
- Инструкции по настройке и использованию
- tests/ — тесты для проверки корректности работы:
- Модульные тесты для отдельных функций
- Интеграционные тесты для проверки взаимодействия компонентов
Рекомендации по именованию файлов:
- Используйте осмысленные имена:
face_detection_with_led.pyлучше, чемproject1.py - Придерживайтесь единого стиля: либо snake_case (face_detection.py), либо camelCase (faceDetection.py)
- Для версионирования используйте суффиксы:
v1_,v2_или даты в формате ГГГГММДД
6.2 Устранение неполадок
Работа с Arduino и компьютерным зрением может сопровождаться различными проблемами. Вот наиболее распространенные из них и способы их решения:
Проблема 1: Arduino не определяется в Python
Симптомы: Ошибка подключения, сообщение "Port not found" или аналогичное.
Решение:
# Способ 1: Автоматическое определение (работает в большинстве случаев)
arduino = SerialObject()
# Способ 2: Ручное указание порта
# Для Windows:
arduino = SerialObject("COM3") # COM3, COM4, COM5 и т.д.
# Для Linux:
arduino = SerialObject("/dev/ttyUSB0") # или /dev/ttyACM0
# Для macOS:
arduino = SerialObject("/dev/cu.usbmodem14101") # или /dev/cu.usbserial-*
# Способ 3: Автоматический поиск порта
import serial.tools.list_ports
def find_arduino_port():
"""Поиск порта Arduino среди доступных последовательных портов"""
ports = serial.tools.list_ports.comports()
for port in ports:
# Проверка по описанию (работает не всегда)
if 'Arduino' in port.description or 'USB Serial' in port.description:
return port.device
# Проверка по производителю (работает не всегда)
if port.manufacturer and ('Arduino' in port.manufacturer or 'FTDI' in port.manufacturer):
return port.device
# Если не нашли, возвращаем первый порт или None
return ports[0].device if ports else None
# Использование функции
port = find_arduino_port()
if port:
arduino = SerialObject(port)
print(f"Arduino найден на порту: {port}")
else:
print("Arduino не найден. Проверьте подключение.")
Дополнительные шаги:
- Проверьте физическое подключение Arduino к компьютеру
- Убедитесь, что на Arduino загружен корректный скетч
- Перезагрузите Arduino (кнопка reset)
- Перезапустите Arduino IDE и Python скрипт
- Попробуйте другой USB кабель (некоторые кабели только для зарядки)
Проблема 2: Ошибки при получении данных
Симптомы: Некорректные данные, пропуски данных, ошибки преобразования типов.
Решение:
def safe_get_data(arduino_object, max_retries=3):
"""Безопасное получение данных с обработкой ошибок"""
for attempt in range(max_retries):
try:
data = arduino_object.getData()
# Проверка наличия данных
if not data:
if attempt == max_retries - 1:
print("Нет данных от Arduino")
continue
# Проверка корректности формата данных
if not isinstance(data, list):
print(f"Некорректный формат данных: {type(data)}")
continue
# Проверка, что список не пустой
if len(data) == 0:
print("Пустой список данных")
continue
# Преобразование первого элемента в число
try:
value = int(data[0])
return value # Успешное получение данных
except ValueError:
print(f"Невозможно преобразовать '{data[0]}' в число")
continue
except Exception as e:
print(f"Ошибка при получении данных (попытка {attempt+1}): {e}")
# Небольшая задержка перед повторной попыткой
import time
time.sleep(0.1)
# Если все попытки неудачны
return None # или значение по умолчанию
# Использование
value = safe_get_data(arduino)
if value is not None:
print(f"Получено значение: {value}")
else:
print("Не удалось получить данные, используем значение по умолчанию")
value = 0
Проблема 3: Камера не открывается или работает некорректно
Симптомы: Черный экран, ошибка открытия камеры, низкий FPS.
Решение:
def init_camera(camera_index=0, width=640, height=480, fps=30):
"""Инициализация камеры с обработкой ошибок"""
cap = None
# Попробуем разные индексы камер
for i in range(5): # Проверяем первые 5 индексов
cap = cv2.VideoCapture(i)
if cap.isOpened():
print(f"Камера найдена на индексе {i}")
# Настройка параметров камеры
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
cap.set(cv2.CAP_PROP_FPS, fps)
# Проверка фактических параметров
actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
actual_fps = cap.get(cv2.CAP_PROP_FPS)
print(f"Разрешение: {actual_width}x{actual_height}")
print(f"FPS: {actual_fps}")
# Тестовый кадр для проверки работы
ret, test_frame = cap.read()
if ret and test_frame is not None:
print("Камера работает корректно")
return cap
else:
print("Камера не возвращает кадры")
cap.release()
continue
else:
if cap:
cap.release()
print("Не удалось инициализировать камеру")
return None
# Использование
cap = init_camera(camera_index=0)
if cap is None:
print("Проверьте:")
print("1. Подключение камеры")
print("2. Драйверы камеры")
print("3. Не используется ли камера другой программой")
exit(1)
Проблема 4: Низкая производительность (низкий FPS)
Симптомы: Медленная работа, задержки в отображении, пропуски кадров.
Решение:
def optimize_performance(cap, target_fps=30):
"""Оптимизация производительности видеозахвата"""
# 1. Уменьшение разрешения (самый эффективный способ)
resolutions = [
(1920, 1080), # Full HD
(1280, 720), # HD
(640, 480), # VGA (рекомендуется для компьютерного зрения)
(320, 240), # QVGA
]
for width, height in resolutions:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(f"Попытка разрешения: {actual_width}x{actual_height}")
# Проверка FPS
import time
start_time = time.time()
frames = 0
# Измеряем FPS в течение 1 секунды
while time.time() - start_time < 1.0 and frames < 30:
ret, frame = cap.read()
if ret:
frames += 1
fps = frames / (time.time() - start_time)
print(f" Достигнутый FPS: {fps:.1f}")
if fps >= target_fps * 0.8: # 80% от целевого FPS
print(f"Выбрано разрешение: {actual_width}x{actual_height} с FPS {fps:.1f}")
break
# 2. Отключение автоматических настроек камеры
# (если камера поддерживает)
try:
cap.set(cv2.CAP_PROP_AUTOFOCUS, 0)
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0)
cap.set(cv2.CAP_PROP_AUTO_WB, 0)
print("Автоматические настройки отключены")
except:
print("Камера не поддерживает ручные настройки")
# 3. Использование буфера для кадров (если необходимо)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # Минимальный буфер
return cap
# В основном цикле также можно оптимизировать:
def optimized_processing_loop(cap, detector):
"""Оптимизированный цикл обработки"""
# Пропуск кадров для увеличения FPS
skip_frames = 1 # Обрабатываем каждый второй кадр
frame_counter = 0
last_processing_time = time.time()
while True:
ret, frame = cap.read()
if not ret:
break
frame_counter += 1
# Пропускаем кадры, если необходимо
if frame_counter % (skip_frames + 1) != 0:
continue
# Обработка только если прошло достаточно времени
current_time = time.time()
if current_time - last_processing_time < 0.033: # ~30 FPS
continue
# Обработка кадра
processed_frame, bboxs = detector.findFaces(frame)
# Отображение
cv2.imshow("Optimized Processing", processed_frame)
last_processing_time = current_time
if cv2.waitKey(1) & 0xFF == ord('q'):
break
6.3 Оптимизация производительности
Производительность критически важна для систем компьютерного зрения, особенно работающих в реальном времени. Вот комплексные рекомендации по оптимизации:
1. Оптимизация кода Python
# ПЛОХО: Медленные операции в цикле
for i in range(1000):
result = expensive_function(data[i]) # Дорогая функция в цикле
# ХОРОШО: Векторизация операций
import numpy as np
data_array = np.array(data)
result = expensive_vectorized_function(data_array) # Векторизованная версия
# ----------------------------------------------------
# ПЛОХО: Частое создание и уничтожение объектов
while True:
detector = FaceDetector() # Создание нового объекта каждый кадр
result = detector.findFaces(frame)
# ХОРОШО: Создание объектов один раз
detector = FaceDetector() # Создание один раз
while True:
result = detector.findFaces(frame) # Многократное использование
# ----------------------------------------------------
# ПЛОХО: Избыточные преобразования изображений
while True:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # Не нужно для OpenCV
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) # Избыточно
# ХОРОШО: Минимальные преобразования
while True:
# OpenCV использует BGR по умолчанию
# Не преобразовывайте без необходимости
processed = process_frame(frame) # Работа с BGR
2. Оптимизация работы с Arduino
// ПЛОХО: Частая отправка данных без проверки изменений
void loop() {
int sensorValue = analogRead(A0);
serialData.send(sensorValue); // Отправка каждого значения
delay(10);
}
// ХОРОШО: Отправка только при изменении значения
int lastSentValue = -1;
void loop() {
int sensorValue = analogRead(A0);
// Отправка только если значение изменилось более чем на 2
if (abs(sensorValue - lastSentValue) > 2) {
serialData.send(sensorValue);
lastSentValue = sensorValue;
}
delay(50); // Более длинная задержка
}
// ----------------------------------------------------
// ПЛОХО: Использование delay() для длительных операций
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(1000); // Блокирующая задержка
digitalWrite(LED_PIN, LOW);
delay(1000);
// Во время delay() Arduino не может делать ничего другого
}
// ХОРОШО: Использование millis() для неблокирующих задержек
unsigned long previousMillis = 0;
const long interval = 1000;
int ledState = LOW;
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(LED_PIN, ledState);
}
// Здесь можно выполнять другие задачи
readSensors();
processData();
sendData();
}
3. Оптимизация использования памяти
# ПЛОХО: Хранение всех кадров в памяти
frames = []
while True:
ret, frame = cap.read()
frames.append(frame.copy()) # Копирование каждого кадра
if len(frames) > 1000:
process_frames(frames) # Обработка всех кадров сразу
# ХОРОШО: Обработка кадров по мере поступления
while True:
ret, frame = cap.read()
if ret:
process_frame(frame) # Обработка сразу, без сохранения
# Если нужно сохранить для анализа, сохраняйте на диск
if need_to_save:
cv2.imwrite(f"frame_{frame_count}.jpg", frame)
# ----------------------------------------------------
# ПЛОХО: Большие структуры данных в глобальной области
BIG_DATA = load_huge_dataset() # Загрузка при запуске
def process():
result = complex_processing(BIG_DATA) # Использование большой структуры
# ХОРОШО: Ленивая загрузка и очистка
class DataProcessor:
def __init__(self):
self.data = None # Не загружаем сразу
def load_data_if_needed(self):
if self.data is None:
self.data = load_huge_dataset() # Загружаем только когда нужно
def process(self):
self.load_data_if_needed()
result = complex_processing(self.data)
# Очистка, если данные больше не нужны
if not self.need_data_anymore():
self.data = None
4. Мониторинг производительности
import psutil
import os
class PerformanceMonitor:
def __init__(self):
self.process = psutil.Process(os.getpid())
self.start_time = time.time()
self.frame_count = 0
def update(self):
"""Обновление статистики производительности"""
self.frame_count += 1
# Расчет FPS
elapsed_time = time.time() - self.start_time
fps = self.frame_count / elapsed_time
# Использование памяти
memory_info = self.process.memory_info()
memory_mb = memory_info.rss / 1024 / 1024 # в МБ
# Загрузка CPU
cpu_percent = self.process.cpu_percent()
return {
'fps': fps,
'memory_mb': memory_mb,
'cpu_percent': cpu_percent,
'frame_count': self.frame_count,
'elapsed_time': elapsed_time
}
def log_performance(self, interval=5):
"""Логирование производительности с заданным интервалом"""
current_time = time.time()
if current_time - self.last_log_time >= interval:
stats = self.update()
print(f"FPS: {stats['fps']:.1f}, "
f"Memory: {stats['memory_mb']:.1f}MB, "
f"CPU: {stats['cpu_percent']:.1f}%")
self.last_log_time = current_time
# Использование в основном цикле
monitor = PerformanceMonitor()
while True:
# Основная обработка
success, frame = cap.read()
if success:
processed_frame = process_frame(frame)
cv2.imshow("Video", processed_frame)
# Мониторинг производительности
monitor.log_performance()
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Вывод итоговой статистики
final_stats = monitor.update()
print("\nИтоговая статистика производительности:")
print(f" Всего кадров: {final_stats['frame_count']}")
print(f" Общее время: {final_stats['elapsed_time']:.1f} сек")
print(f" Средний FPS: {final_stats['fps']:.1f}")
print(f" Пиковое использование памяти: {final_stats['memory_mb']:.1f} MB")
Заключение
Этот курс охватывает основные аспекты интеграции Arduino и OpenCV Python для создания проектов компьютерного зрения. Мы прошли путь от базовых концепций Arduino до создания сложных систем, объединяющих аппаратное обеспечение и программные алгоритмы компьютерного зрения.
Ключевые достижения курса:
- Освоение основ Arduino: вы научились работать с цифровыми и аналоговыми сигналами, программировать микроконтроллеры и понимать их архитектуру.
- Интеграция Arduino и Python: вы освоили методы связи между микроконтроллерами и высокоуровневыми языками программирования, что открывает возможности для создания сложных распределенных систем.
- Основы компьютерного зрения: вы изучили базовые алгоритмы обработки изображений, распознавания объектов и работы с видеопотоками в реальном времени.
- Практические проекты: вы создали несколько работающих систем:
- Управление LED через последовательный порт
- Визуализация данных с аналоговых датчиков
- Системы распознавания лиц с различными типами индикации
- Профессиональные практики: вы познакомились с методами оптимизации производительности, отладки сложных систем и организации проектов.
Перспективы развития:
- Расширение функциональности компьютерного зрения:
- Распознавание жестов и поз (используя MediaPipe Hands и Pose)
- Отслеживание объектов в реальном времени
- Распознавание эмоций по выражению лица
- Optical Character Recognition (OCR) для чтения текста
- Интеграция с дополнительными устройствами:
- Сервомоторы для систем слежения
- Ультразвуковые датчики для определения расстояния
- Датчики температуры и влажности
- Беспроводные модули (Wi-Fi, Bluetooth)
- Создание сложных систем:
- Системы безопасности с распознаванием лиц
- Роботы с компьютерным зрением
- Интерактивные инсталляции
- Промышленные системы контроля качества
- Оптимизация и развертывание:
- Перенос на более мощные микроконтроллеры (Raspberry Pi, Jetson Nano)
- Использование нейронных сетей для более точного распознавания
- Создание веб-интерфейсов для удаленного управления
- Оптимизация для работы на edge-устройствах
Философия разработки:
Помните, что создание проектов на стыке аппаратного и программного обеспечения — это искусство баланса. Успешный проект:
- Надежен: работает стабильно в различных условиях
- Эффективен: использует ресурсы оптимально
- Масштабируем: может быть расширен при необходимости
- Поддерживаем: код понятен и хорошо документирован
- Полезен: решает реальные задачи
Заключительные слова:
Компьютерное зрение с Arduino — это не просто технический навык, это способ мышления. Вы научились видеть проблемы комплексно, находить решения на стыке дисциплин и создавать системы, которые взаимодействуют с физическим миром. Эти навыки будут востребованы в самых разных областях: от робототехники и автоматизации до интернета вещей и умных городов.
Продолжайте экспериментировать, учиться и создавать. Каждый проект, даже неудачный, делает вас лучше как разработчика. Сообщество Arduino и OpenCV огромно и готово помочь — не стесняйтесь задавать вопросы, делиться своими достижениями и учиться у других.
Удачи в ваших будущих проектах! Помните, что границы возможного определяются только вашим воображением и настойчивостью.
↑ К оглавлениюЧек-лист для самопроверки знаний:
| ✓ | Задача |
|---|---|
| Я понимаю разницу между цифровыми и аналоговыми сигналами в Arduino | |
| Я могу установить Arduino IDE и настроить среду разработки | |
| Я умею управлять светодиодом через Python с использованием CVZone | |
| Я могу читать данные с потенциометра и визуализировать их в Python | |
| Я понимаю принципы работы системы распознавания лиц с MediaPipe | |
| Я могу интегрировать компьютерное зрение с управлением Arduino | |
| Я знаю, как оптимизировать производительность Python-скриптов | |
| Я умею организовывать проекты с Arduino и Python | |
| Я понимаю, как устранять типичные неполадки в системе | |
| Я готов создавать собственные проекты на основе полученных знаний |
Ссылки по теме
- OPENCV НА PYTHON | Часть 1 | БАЗОВЫЕ ЗНАНИЯ
- OPENCV НА PYTHON | Часть 2 | БАЗОВЫЕ ЗНАНИЯ
- OPENCV НА PYTHON | Часть 3 | БАЗОВЫЕ ЗНАНИЯ

