Перейти к содержимому

OPENCV НА PYTHON | Часть 2 | БАЗОВЫЕ ЗНАНИЯ

Уже изучил: OPENCV НА PYTHON | Часть 2 | БАЗОВЫЕ ЗНАНИЯ

Урок 8: Обнаружение цветов с помощью HSV цветового пространства

8.1 Теоретическая основа HSV цветового пространства

Суть задачи: Научиться эффективно детектировать объекты по цвету, используя HSV цветовое пространство вместо RGB/BGR.
Теоретическая основа:

HSV (Hue, Saturation, Value) - цветовое пространство, более интуитивное для восприятия человеком:

  • Hue (Оттенок): Определяет основной цвет (0-179 в OpenCV, вместо 0-360 в реальности)
  • Saturation (Насыщенность): Степень чистоты цвета (0-255), 0 = серый, 255 = полностью насыщенный
  • Value (Яркость): Яркость цвета (0-255), 0 = черный, 255 = максимально яркий

Преимущества HSV перед BGR:

  • Независимость от освещения: Hue остается стабильным при изменении яркости
  • Интуитивная настройка: легко определить диапазон цветов
  • Эффективная сегментация: простое выделение объектов по цвету
Практическое применение: Детектирование цветных маркеров, распознавание объектов по цвету, сегментация изображений.

К чек-листу

8.2 Создание интерактивных трекбаров для настройки параметров

Суть задачи: Разработать интерфейс для интерактивной настройки параметров цветового детектирования в реальном времени.

Используемые методы:

  • cv2.namedWindow(window_name) - создание именованного окна
  • cv2.resizeWindow(window_name, width, height) - изменение размера окна
  • cv2.createTrackbar(name, window, value, count, onChange) - создание трекбара:
    • name - название трекбара
    • window - окно, где разместить трекбар
    • value - начальное значение
    • count - максимальное значение
    • onChange - функция обратного вызова (может быть пустой)

Практический пример:

import cv2
import numpy as np

# Создание окна для трекбаров
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars", 640, 240)

# Пустая функция для обратного вызова
def empty(a):
    pass

# Создание трекбаров для настройки HSV диапазона
cv2.createTrackbar("Hue Min", "TrackBars", 0, 179, empty)
cv2.createTrackbar("Hue Max", "TrackBars", 19, 179, empty)
cv2.createTrackbar("Sat Min", "TrackBars", 110, 255, empty)
cv2.createTrackbar("Sat Max", "TrackBars", 240, 255, empty)
cv2.createTrackbar("Val Min", "TrackBars", 153, 255, empty)
cv2.createTrackbar("Val Max", "TrackBars", 255, 255, empty)

while True:
    # Захват кадра с веб-камеры
    success, img = cap.read()
    if not success:
        break
    
    # Конвертация в HSV
    imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    # Получение значений трекбаров
    h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
    h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
    s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
    s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
    v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
    v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
    
    # Вывод текущих параметров
    print(f"Hue: {h_min}-{h_max}, Sat: {s_min}-{s_max}, Val: {v_min}-{v_max}")
    
    # Отображение изображения
    cv2.imshow("Original", img)
    cv2.imshow("HSV", imgHSV)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
Важные особенности:
  • В OpenCV Hue имеет диапазон 0-179 (вместо 0-360) для экономии памяти
  • Трекбары позволяют визуально подобрать оптимальные параметры для конкретного цвета
  • Функция обратного вызова обязательна, даже если она пустая
  • Значения трекбаров нужно получать в цикле для реального времени

К чек-листу

8.3 Создание масок и применение цветовой фильтрации

Суть задачи: Научиться создавать бинарные маски для выделения областей заданного цвета.

Используемые методы:

  • cv2.inRange(src, lowerb, upperb) - создание маски в заданном диапазоне:
    • src - исходное изображение (обычно HSV)
    • lowerb - нижняя граница диапазона (numpy массив)
    • upperb - верхняя граница диапазона (numpy массив)
  • cv2.bitwise_and(src1, src2, mask) - побитовое И для наложения маски:
    • src1, src2 - исходные изображения
    • mask - бинарная маска для фильтрации

Практический пример:

import cv2
import numpy as np

# Чтение изображения
img = cv2.imread('resources/lambo.png')

# Конвертация в HSV
imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# Определение диапазона для оранжевого цвета
lower_orange = np.array([5, 50, 50])   # [H_min, S_min, V_min]
upper_orange = np.array([15, 255, 255]) # [H_max, S_max, V_max]

# Создание маски
mask = cv2.inRange(imgHSV, lower_orange, upper_orange)

# Применение маски к исходному изображению
result = cv2.bitwise_and(img, img, mask=mask)

# Объединение изображений для визуализации
imgStack = stackImages(0.6, ([img, imgHSV], [mask, result]))
cv2.imshow("Color Detection", imgStack)
cv2.waitKey(0)
cv2.destroyAllWindows()
Важные особенности:
  • Маска - бинарное изображение (0 и 255), где 255 соответствует пикселям в диапазоне
  • Побитовое И сохраняет только те пиксели исходного изображения, где маска равна 255
  • Для разных цветов нужны разные диапазоны HSV:
    • Красный: ~[0-10, 100-255, 100-255] и [160-179, 100-255, 100-255] (из-за кругового Hue)
    • Зеленый: ~[40-80, 100-255, 100-255]
    • Синий: ~[90-130, 100-255, 100-255]
  • Освещение сильно влияет на параметры V (яркость)

К чек-листу

8.4 Генерическое решение для детектирования нескольких цветов

Суть задачи: Создать универсальное решение для одновременного детектирования нескольких цветов.

Практический пример:

import cv2
import numpy as np

# Список цветов для детектирования: [[h_min, s_min, v_min, h_max, s_max, v_max, color_name], ...]
myColors = [
    [5, 107, 0, 19, 255, 255, "Orange"],
    [133, 56, 0, 159, 156, 255, "Purple"], 
    [57, 76, 0, 100, 255, 255, "Green"],
    [105, 50, 50, 130, 255, 255, "Blue"]
]

# Список цветов для отображения (BGR)
myColorValues = [
    [51, 153, 255],   # Оранжевый
    [255, 0, 255],    # Фиолетовый
    [0, 255, 0],      # Зеленый
    [255, 0, 0]       # Синий
]

def findColor(img, myColors, myColorValues):
    imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    count = 0
    newPoints = []
    
    for color in myColors:
        lower = np.array(color[0:3])
        upper = np.array(color[3:6])
        mask = cv2.inRange(imgHSV, lower, upper)
        
        # Поиск контуров для определения позиции цвета
        contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        for cnt in contours:
            area = cv2.contourArea(cnt)
            if area > 500:  # Фильтрация по площади для удаления шума
                x, y, w, h = cv2.boundingRect(cnt)
                cv2.rectangle(img, (x, y), (x + w, y + h), myColorValues[count], 2)
                newPoints.append([x + w // 2, y, count])  # Центр верхней границы + индекс цвета
        
        count += 1
    
    return newPoints

# Использование функции
cap = cv2.VideoCapture(0)
myPoints = []  # [[x, y, colorId], ...]

while True:
    success, img = cap.read()
    if not success:
        break
    
    newPoints = findColor(img, myColors, myColorValues)
    
    # Сохранение точек для рисования
    if newPoints:
        for point in newPoints:
            myPoints.append(point)
    
    # Рисование всех точек
    for point in myPoints:
        cv2.circle(img, (point[0], point[1]), 10, myColorValues[point[2]], cv2.FILLED)
    
    cv2.imshow("Result", img)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
Важные особенности:
  • Решение легко расширяемо: для добавления нового цвета достаточно добавить его в списки myColors и myColorValues
  • Фильтрация по площади (area > 500) удаляет мелкий шум и ложные срабатывания
  • Центр верхней границы объекта (x + w // 2, y) используется как точка рисования
  • Индекс цвета сохраняется для правильного отображения

К чек-листу

Урок 9: Обнаружение контуров и классификация геометрических фигур

9.1 Теоретическая основа контурного анализа

Суть задачи: Научиться находить и анализировать контуры объектов для их идентификации и классификации.
Теоретическая основа:
  • Контур - непрерывная линия или кривая, соединяющая точки с одинаковой интенсивностью, обычно граница объекта
  • Иерархия контуров - отношения между контурами (родитель-потомок) для вложенных объектов

Методы поиска контуров:

  • cv2.RETR_EXTERNAL - только внешние контуры
  • cv2.RETR_LIST - все контуры без иерархии
  • cv2.RETR_TREE - все контуры с полной иерархией
  • cv2.RETR_CCOMP - все контуры с двумя уровнями иерархии

Методы аппроксимации контуров:

  • cv2.CHAIN_APPROX_NONE - все точки контура
  • cv2.CHAIN_APPROX_SIMPLE - сжатие вертикальных, горизонтальных и диагональных сегментов
Практическое применение: Распознавание геометрических фигур, подсчет объектов, измерение площадей, распознавание символов.

К чек-листу

9.2 Поиск контуров и вычисление их характеристик

Суть задачи: Реализовать алгоритм для нахождения контуров и вычисления их основных характеристик.

Используемые методы:

  • cv2.findContours(image, mode, method) - поиск контуров
  • cv2.contourArea(contour) - вычисление площади контура
  • cv2.arcLength(contour, closed) - вычисление длины контура
  • cv2.approxPolyDP(contour, epsilon, closed) - аппроксимация контура многоугольником
  • cv2.boundingRect(contour) - получение ограничивающего прямоугольника
  • cv2.drawContours(image, contours, contourIdx, color, thickness) - отрисовка контуров

Практический пример:

import cv2
import numpy as np

def getContours(img, imgContour):
    # Поиск контуров
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    
    for cnt in contours:
        # Вычисление площади контура
        area = cv2.contourArea(cnt)
        print(f"Area: {area}")
        
        if area > 500:  # Фильтрация по площади
            # Отрисовка контура
            cv2.drawContours(imgContour, cnt, -1, (255, 0, 0), 3)
            
            # Вычисление длины контура (периметра)
            peri = cv2.arcLength(cnt, True)
            print(f"Perimeter: {peri}")
            
            # Аппроксимация контура
            approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
            print(f"Corner points: {len(approx)}")
            
            # Получение ограничивающего прямоугольника
            x, y, w, h = cv2.boundingRect(approx)
            cv2.rectangle(imgContour, (x, y), (x + w, y + h), (0, 255, 0), 2)
            
            # Определение типа фигуры
            objType = "Unknown"
            if len(approx) == 3:
                objType = "Triangle"
            elif len(approx) == 4:
                # Проверка на квадрат: отношение сторон близко к 1
                aspRatio = w / float(h)
                if 0.95 < aspRatio < 1.05:
                    objType = "Square"
                else:
                    objType = "Rectangle"
            elif len(approx) > 4:
                objType = "Circle"
            
            # Отображение типа фигуры
            cv2.putText(imgContour, objType, 
                       (x + (w // 2) - 30, y + (h // 2)), 
                       cv2.FONT_HERSHEY_COMPLEX, 0.7, (0, 0, 0), 2)

# Основной код
img = cv2.imread('resources/shapes.png')
imgContour = img.copy()

# Предварительная обработка
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
imgBlur = cv2.GaussianBlur(imgGray, (7, 7), 1)
imgCanny = cv2.Canny(imgBlur, 50, 50)

# Поиск контуров
getContours(imgCanny, imgContour)

# Отображение результатов
imgStack = stackImages(0.6, ([img, imgGray, imgBlur], [imgCanny, imgContour, np.zeros_like(img)]))
cv2.imshow("Shape Detection", imgStack)
cv2.waitKey(0)
cv2.destroyAllWindows()
Важные особенности:
  • Контурный анализ работает с бинарными изображениями (результат Canny или threshold)
  • Фильтрация по площади (area > 500) удаляет мелкие контуры-шум
  • epsilon = 0.02 * peri - стандартное значение для аппроксимации (2% от периметра)
  • Для распознавания квадратов проверяется отношение сторон (aspRatio)
  • cv2.drawContours с contourIdx = -1 рисует все контуры в списке

К чек-листу

9.3 Расширенный анализ контуров и их свойств

Суть задачи: Изучить дополнительные свойства контуров для более точной классификации объектов.

Центр массы контура:

M = cv2.moments(cnt)
if M["m00"] != 0:  # Проверка деления на ноль
    cx = int(M["m10"] / M["m00"])
    cy = int(M["m01"] / M["m00"])

Выпуклая оболочка:

hull = cv2.convexHull(cnt)
cv2.drawContours(img, [hull], -1, (0, 255, 0), 2)

Дефекты выпуклости (для распознавания пальцев):

hull = cv2.convexHull(cnt, returnPoints=False)
defects = cv2.convexityDefects(cnt, hull)

Минимальная площадь и минимальный круг:

rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)

(x, y), radius = cv2.minEnclosingCircle(cnt)

Эллипс минимальной площади:

ellipse = cv2.fitEllipse(cnt)
Важные особенности:
  • Моменты (cv2.moments) предоставляют информацию о форме и распределении массы
  • Выпуклая оболочка помогает определить "вогнутости" объекта
  • Минимальная ограничивающая фигура полезна для нормализации ориентации
  • Дефекты выпуклости применяются в распознавании жестов рук

К чек-листу

Урок 10: Детектирование лиц с помощью каскадов Хаара

10.1 Теоретическая основа метода Виолы-Джонса

Суть задачи: Понять принцип работы алгоритма Виолы-Джонса для детектирования объектов в реальном времени.
Теоретическая основа:

Исторический контекст: Алгоритм Виолы-Джонса (2001) был первым, обеспечивающим детектирование лиц в реальном времени

Основные компоненты:

  1. Интегральное изображение - быстрое вычисление суммы пикселей в прямоугольнике
  2. Каскад классификаторов - последовательность простых классификаторов
  3. Признаки Хаара - простые прямоугольные признаки для описания локальных особенностей
  4. Алгоритм AdaBoost - выбор лучших признаков и создание сильного классификатора

Преимущества метода:

  • Высокая скорость работы
  • Хорошая точность для фронтальных лиц
  • Низкие требования к вычислительным ресурсам

Ограничения:

  • Плохо работает с повернутыми лицами
  • Требует предварительно обученных каскадов
  • Может давать ложные срабатывания
Практическое применение: Системы безопасности, фотоаппараты, социальные сети, системы контроля доступа.

К чек-листу

10.2 Практическая реализация детектирования лиц

Суть задачи: Реализовать детектирование лиц с использованием предобученных каскадов Хаара.

Используемые методы:

  • cv2.CascadeClassifier(filename) - загрузка предобученного каскада
  • detectMultiScale(image, scaleFactor, minNeighbors, minSize, maxSize) - детектирование объектов

Практический пример:

import cv2

# Загрузка предобученного каскада для детектирования лиц
faceCascade = cv2.CascadeClassifier("resources/haarcascade_frontalface_default.xml")

# Загрузка изображения
img = cv2.imread('resources/lena.png')
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Детектирование лиц
faces = faceCascade.detectMultiScale(
    imgGray,
    scaleFactor=1.1,    # Уменьшение масштаба изображения на 10% на каждом шаге
    minNeighbors=4,     # Минимальное количество соседних прямоугольников для подтверждения
    minSize=(30, 30)    # Минимальный размер лица
)

# Отрисовка прямоугольников вокруг обнаруженных лиц
for (x, y, w, h) in faces:
    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
    cv2.putText(img, "Face", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 0, 0), 2)

# Детектирование глаз на том же изображении
eyeCascade = cv2.CascadeClassifier("resources/haarcascade_eye.xml")
for (x, y, w, h) in faces:
    roiGray = imgGray[y:y + h, x:x + w]
    roiColor = img[y:y + h, x:x + w]
    
    eyes = eyeCascade.detectMultiScale(roiGray, scaleFactor=1.1, minNeighbors=5)
    for (ex, ey, ew, eh) in eyes:
        cv2.rectangle(roiColor, (ex, ey), (ex + ew, ey + eh), (0, 255, 0), 2)

# Отображение результата
cv2.imshow("Face Detection", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Важные особенности:
  • Всегда конвертируйте изображение в градации серого перед детектированием
  • scaleFactor=1.1 означает уменьшение масштаба на 10% на каждом шаге
  • minNeighbors=4 требует минимум 4 совпадающих прямоугольника для подтверждения
  • Для детектирования глаз используйте ROI (Region of Interest) - область лица
  • Предобученные каскады: haarcascade_frontalface_default.xml, haarcascade_eye.xml, haarcascade_smile.xml

К чек-листу

10.3 Детектирование лиц в реальном времени

Суть задачи: Реализовать систему детектирования лиц с веб-камеры в реальном времени.

Практический пример:

import cv2

# Загрузка каскадов
faceCascade = cv2.CascadeClassifier("resources/haarcascade_frontalface_default.xml")
eyeCascade = cv2.CascadeClassifier("resources/haarcascade_eye.xml")

# Инициализация веб-камеры
cap = cv2.VideoCapture(0)
cap.set(3, 640)  # Ширина
cap.set(4, 480)  # Высота
cap.set(10, 150)  # Яркость

while True:
    success, img = cap.read()
    if not success:
        break
    
    imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # Детектирование лиц
    faces = faceCascade.detectMultiScale(imgGray, 1.1, 4)
    
    # Обработка каждого обнаруженного лица
    for (x, y, w, h) in faces:
        cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
        cv2.putText(img, f"Face {w}x{h}", (x, y - 10), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
        
        # ROI для детектирования глаз
        roiGray = imgGray[y:y + h, x:x + w]
        roiColor = img[y:y + h, x:x + w]
        
        # Детектирование глаз
        eyes = eyeCascade.detectMultiScale(roiGray, 1.1, 5)
        for (ex, ey, ew, eh) in eyes:
            cv2.rectangle(roiColor, (ex, ey), (ex + ew, ey + eh), (0, 255, 0), 2)
    
    # Отображение количества обнаруженных лиц
    cv2.putText(img, f"Faces detected: {len(faces)}", (20, 450), 
               cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
    
    cv2.imshow("Face Detection", img)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
Важные особенности:
  • В реальном времени важно балансировать между скоростью и точностью
  • scaleFactor=1.1 и minNeighbors=4 - хороший баланс для веб-камеры
  • Отображение размера лица помогает в настройке параметров
  • Для мобильных устройств можно уменьшить разрешение кадра для повышения производительности

К чек-листу

Урок 11: Виртуальная рисовальная доска (Проект 1)

11.1 Постановка задачи и архитектура проекта

Суть проекта: Создать систему виртуальной рисовальной доски, где пользователь может рисовать в воздухе с помощью цветных маркеров.

Требования к системе:

  • Работа в реальном времени с веб-камерой
  • Детектирование нескольких цветов одновременно
  • Плавное рисование с минимальной задержкой
  • Возможность стирания и очистки холста
  • Интуитивный интерфейс пользователя

Архитектура проекта:

  1. Входной модуль: Захват видео с веб-камеры
  2. Модуль обработки: Детектирование цветов и позиций маркеров
  3. Модуль рисования: Управление точками и отрисовка на холсте
  4. Модуль интерфейса: Отображение результатов и управление

К чек-листу

11.2 Реализация основного функционала

Суть задачи: Реализовать детектирование цветных маркеров и их позиций для системы рисования.

Ключевые компоненты:

import cv2
import numpy as np

# Конфигурация цветов
myColors = [
    [5, 107, 0, 19, 255, 255, "Orange"],   # [Hmin, Smin, Vmin, Hmax, Smax, Vmax, Name]
    [133, 56, 0, 159, 156, 255, "Purple"],
    [57, 76, 0, 100, 255, 255, "Green"],
    [105, 50, 50, 130, 255, 255, "Blue"]
]

# Цвета для рисования (BGR)
myColorValues = [
    [51, 153, 255],   # Оранжевый
    [255, 0, 255],    # Фиолетовый
    [0, 255, 0],      # Зеленый
    [255, 0, 0]       # Синий
]

# Холст для рисования
myPoints = []  # [[x, y, colorId], ...]

def findColor(img, myColors, myColorValues):
    imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    newPoints = []
    
    for i, color in enumerate(myColors):
        lower = np.array(color[0:3])
        upper = np.array(color[3:6])
        mask = cv2.inRange(imgHSV, lower, upper)
        
        # Улучшение маски с помощью морфологических операций
        kernel = np.ones((5, 5), np.uint8)
        mask = cv2.erode(mask, kernel, iterations=1)
        mask = cv2.dilate(mask, kernel, iterations=2)
        
        # Поиск контуров
        contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # Нахождение самого большого контура
        max_area = 0
        max_cnt = None
        
        for cnt in contours:
            area = cv2.contourArea(cnt)
            if area > 1000 and area > max_area:
                max_area = area
                max_cnt = cnt
        
        if max_cnt is not None:
            x, y, w, h = cv2.boundingRect(max_cnt)
            cx, cy = x + w // 2, y
            cv2.circle(imgResult, (cx, cy), 15, myColorValues[i], cv2.FILLED)
            newPoints.append([cx, cy, i])
    
    return newPoints

def drawOnCanvas(myPoints, myColorValues):
    for point in myPoints:
        cv2.circle(imgResult, (point[0], point[1]), 10, myColorValues[point[2]], cv2.FILLED)

# Основной цикл
cap = cv2.VideoCapture(0)
cap.set(3, 1280)
cap.set(4, 720)
cap.set(10, 150)

while True:
    success, img = cap.read()
    if not success:
        break
    
    img = cv2.flip(img, 1)  # Зеркальное отображение
    imgResult = img.copy()
    
    newPoints = findColor(img, myColors, myColorValues)
    
    if newPoints:
        for point in newPoints:
            if myPoints:
                last_point = myPoints[-1]
                if point[2] == last_point[2]:
                    distance = np.sqrt((point[0] - last_point[0])**2 + 
                                      (point[1] - last_point[1])**2)
                    if distance < 100:
                        myPoints.append(point)
            else:
                myPoints.append(point)
    
    if myPoints:
        drawOnCanvas(myPoints, myColorValues)
    
    cv2.putText(imgResult, "Virtual Paint", (10, 50), 
               cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3)
    cv2.putText(imgResult, "Press 'c' to clear", (10, 100), 
               cv2.FONT_HERSHEY_SIMPLEX, 0.7, (200, 200, 200), 2)
    
    cv2.imshow("Virtual Paint", imgResult)
    
    key = cv2.waitKey(1)
    if key == ord('c'):
        myPoints = []
    elif key == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
Важные особенности:
  • Зеркальное отображение (cv2.flip(img, 1)) делает интерфейс более интуитивным
  • Морфологические операции улучшают качество маски и удаляют шум
  • Фильтрация по площади (area > 1000) отсекает мелкие объекты
  • Сглаживание траектории предотвращает резкие скачки при рисовании
  • Управление с клавиатуры: 'c' для очистки, 'q' для выхода

К чек-листу

11.3 Оптимизация и улучшение проекта

Суть задачи: Улучшить качество рисования и добавить дополнительные функции.

Оптимизации:

  • Сглаживание траектории: Использование скользящего среднего для плавного рисования
  • Адаптивная чувствительность: Изменение параметров детектирования в зависимости от освещения
  • Фильтрация дрожания: Удаление аномальных значений позиций
  • Управление толщиной линии: Изменение диаметра кисти

Дополнительные функции:

# Глобальные переменные для новых функций
brushThickness = 15
eraserThickness = 50
drawColor = (255, 0, 255)
xp, yp = 0, 0
imgCanvas = np.zeros((720, 1280, 3), np.uint8)

# Внутри основного цикла:
if newPoints:
    for point in newPoints:
        x, y = point[0:2]
        
        if xp == 0 and yp == 0:
            xp, yp = x, y
        
        cv2.line(imgCanvas, (xp, yp), (x, y), myColorValues[point[2]], brushThickness)
        xp, yp = x, y
else:
    xp, yp = 0, 0

# Наложение холста на изображение камеры
imgGray = cv2.cvtColor(imgCanvas, cv2.COLOR_BGR2GRAY)
_, imgInv = cv2.threshold(imgGray, 50, 255, cv2.THRESH_BINARY_INV)
imgInv = cv2.cvtColor(imgInv, cv2.COLOR_GRAY2BGR)
imgResult = cv2.bitwise_and(img, imgInv)
imgResult = cv2.bitwise_or(imgResult, imgCanvas)
Важные особенности:
  • Разделение холста и изображения камеры позволяет сохранять качество рисунка
  • Плавное рисование с линиями вместо отдельных точек создает непрерывные линии
  • Наложение холста с помощью битовых операций создает эффект рисования на изображении

К чек-листу

Урок 12: Сканер документов (Проект 2)

12.1 Постановка задачи и теоретическая основа

Суть проекта: Создать систему сканирования документов с коррекцией перспективы для получения изображения вида "сверху".

Требования к системе:

  • Автоматическое детектирование документа на изображении
  • Коррекция перспективы для получения прямоугольного вида
  • Улучшение качества изображения для лучшей читаемости
  • Работа с документами разного формата и ориентации
Теоретическая основа:
  • Перспективное преобразование: Математическое преобразование, корректирующее искажение перспективы
  • Гомография: Проективное преобразование между двумя плоскостями
  • Алгоритм обнаружения углов: Поиск характерных точек документа
  • Адаптивная пороговая обработка: Улучшение контрастности для лучшего детектирования

К чек-листу

12.2 Предварительная обработка изображения

Суть задачи: Подготовить изображение для детектирования документа с помощью серии последовательных преобразований.

Используемые методы:

  • Конвертация в градации серого (cv2.cvtColor)
  • Размытие по Гауссу (cv2.GaussianBlur)
  • Детектирование границ (cv2.Canny)
  • Дилатация (cv2.dilate)
  • Эрозия (cv2.erode)

Практический пример:

import cv2
import numpy as np

def preProcessing(img):
    """
    Предварительная обработка изображения для детектирования документа
    """
    img = cv2.resize(img, (widthImg, heightImg))
    imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    imgBlur = cv2.GaussianBlur(imgGray, (5, 5), 1)
    imgCanny = cv2.Canny(imgBlur, 200, 200)
    kernel = np.ones((5, 5), np.uint8)
    imgDilate = cv2.dilate(imgCanny, kernel, iterations=2)
    imgEroded = cv2.erode(imgDilate, kernel, iterations=1)
    return imgEroded

# Параметры изображения
widthImg = 640
heightImg = 480

cap = cv2.VideoCapture(0)
cap.set(3, widthImg)
cap.set(4, heightImg)
cap.set(10, 150)

while True:
    success, img = cap.read()
    if not success:
        break
    
    imgProcessed = preProcessing(img.copy())
    
    imgStack = stackImages(0.6, ([img, imgProcessed], 
                               [np.zeros_like(img), np.zeros_like(img)]))
    
    cv2.imshow("Document Scanner", imgStack)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
Важные особенности:
  • Последовательность операций критична: сначала размытие, потом детектирование границ
  • Размер ядра (5, 5) подходит для большинства документов
  • Пороги Canny (200, 200) обеспечивают детектирование только значимых границ

К чек-листу

12.3 Детектирование контуров документа

Суть задачи: Найти контур документа и определить его угловые точки для перспективного преобразования.

Практический пример:

def getContours(img, imgOriginal):
    """
    Поиск контура документа и определение его угловых точек
    """
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    biggest = np.array([])
    maxArea = 0
    imgWithContours = imgOriginal.copy()
    
    for cnt in contours:
        area = cv2.contourArea(cnt)
        
        if area > 5000:
            peri = cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
            
            if area > maxArea and len(approx) == 4:
                biggest = approx
                maxArea = area
        
        cv2.drawContours(imgWithContours, cnt, -1, (0, 255, 0), 2)
    
    if biggest.size != 0:
        biggest = biggest.reshape((4, 2))
        add = biggest.sum(1)
        diff = np.diff(biggest, axis=1)
        
        pts = np.zeros((4, 1, 2), dtype=np.int32)
        pts[0] = biggest[np.argmin(add)]    # Верхний левый
        pts[3] = biggest[np.argmax(add)]    # Нижний правый
        pts[1] = biggest[np.argmin(diff)]   # Верхний правый
        pts[2] = biggest[np.argmax(diff)]   # Нижний левый
        
        cv2.drawContours(imgWithContours, pts, -1, (0, 0, 255), 20)
        cv2.drawContours(imgWithContours, [pts], -1, (255, 0, 255), 10)
        
        return pts, imgWithContours
    
    return biggest, imgWithContours
Важные особенности:
  • Фильтрация по площади (area > 5000) отсекает мелкие объекты
  • Проверка на четырехугольник (len(approx) == 4) гарантирует детектирование документа
  • Правильная сортировка углов критична для корректного перспективного преобразования

К чек-листу

12.4 Перспективное преобразование и постобработка

Суть задачи: Применить перспективное преобразование для получения вида документа "сверху" и улучшить качество изображения.

Полный код проекта:

import cv2
import numpy as np

widthImg = 640
heightImg = 480

def preProcessing(img):
    img = cv2.resize(img, (widthImg, heightImg))
    imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    imgBlur = cv2.GaussianBlur(imgGray, (5, 5), 1)
    imgCanny = cv2.Canny(imgBlur, 200, 200)
    kernel = np.ones((5, 5), np.uint8)
    imgDilate = cv2.dilate(imgCanny, kernel, iterations=2)
    imgEroded = cv2.erode(imgDilate, kernel, iterations=1)
    return imgEroded

def getContours(img, imgOriginal):
    contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    biggest = np.array([])
    maxArea = 0
    imgWithContours = imgOriginal.copy()
    
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area > 5000:
            peri = cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
            if area > maxArea and len(approx) == 4:
                biggest = approx
                maxArea = area
        cv2.drawContours(imgWithContours, cnt, -1, (0, 255, 0), 2)
    
    if biggest.size != 0:
        biggest = biggest.reshape((4, 2))
        add = biggest.sum(1)
        diff = np.diff(biggest, axis=1)
        
        pts = np.zeros((4, 1, 2), dtype=np.int32)
        pts[0] = biggest[np.argmin(add)]
        pts[3] = biggest[np.argmax(add)]
        pts[1] = biggest[np.argmin(diff)]
        pts[2] = biggest[np.argmax(diff)]
        
        cv2.drawContours(imgWithContours, pts, -1, (0, 0, 255), 20)
        cv2.drawContours(imgWithContours, [pts], -1, (255, 0, 255), 10)
        
        return pts, imgWithContours
    
    return biggest, imgWithContours

def getWarp(img, biggest):
    """Применение перспективного преобразования"""
    if biggest.size == 0:
        return img
    
    pts1 = np.float32(biggest)
    pts2 = np.float32([[0, 0], [widthImg, 0], [0, heightImg], [widthImg, heightImg]])
    
    matrix = cv2.getPerspectiveTransform(pts1, pts2)
    imgOutput = cv2.warpPerspective(img, matrix, (widthImg, heightImg))
    
    imgOutput = imgOutput[20:imgOutput.shape[0] - 20, 20:imgOutput.shape[1] - 20]
    imgOutput = cv2.resize(imgOutput, (widthImg, heightImg))
    
    return imgOutput

def enhanceDocument(img):
    """Улучшение качества документа для лучшей читаемости"""
    imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    imgEnhanced = cv2.adaptiveThreshold(imgGray, 255, 
                                       cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
                                       cv2.THRESH_BINARY, 11, 2)
    imgEnhanced = cv2.cvtColor(imgEnhanced, cv2.COLOR_GRAY2BGR)
    return imgEnhanced

# Основной цикл программы
cap = cv2.VideoCapture(0)
cap.set(3, widthImg)
cap.set(4, heightImg)
cap.set(10, 150)

docScanMode = False
resultImage = None

while True:
    success, img = cap.read()
    if not success:
        break
    
    img = cv2.resize(img, (widthImg, heightImg))
    imgOriginal = img.copy()
    imgProcessed = preProcessing(img)
    
    if docScanMode:
        biggest, imgWithContours = getContours(imgProcessed, imgOriginal)
        
        if biggest.size != 0:
            imgWarped = getWarp(imgOriginal, biggest)
            imgEnhanced = enhanceDocument(imgWarped)
            
            imgStack = stackImages(0.6, ([imgOriginal, imgProcessed, imgWithContours],
                                       [imgWarped, imgEnhanced, np.zeros_like(img)]))
            resultImage = imgEnhanced.copy()
        else:
            imgStack = stackImages(0.6, ([imgOriginal, imgProcessed, imgWithContours],
                                       [np.zeros_like(img), np.zeros_like(img), np.zeros_like(img)]))
    else:
        if resultImage is not None:
            imgStack = stackImages(0.8, ([imgOriginal, resultImage],
                                       [np.zeros_like(img), np.zeros_like(img)]))
        else:
            imgStack = stackImages(0.8, ([imgOriginal, imgProcessed],
                                       [np.zeros_like(img), np.zeros_like(img)]))
    
    cv2.putText(imgStack, f"Mode: {'Scanning' if docScanMode else 'Viewing'}", 
               (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
    cv2.putText(imgStack, "Press 's' to scan / 'v' to view / 'q' to quit", 
               (10, heightImg - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
    
    cv2.imshow("Document Scanner", imgStack)
    
    key = cv2.waitKey(1)
    if key == ord('q'):
        break
    elif key == ord('s'):
        docScanMode = True
    elif key == ord('v'):
        docScanMode = False
    elif key == ord('r'):
        resultImage = None

cap.release()
cv2.destroyAllWindows()
Важные особенности:
  • Интерактивный интерфейс с переключением режимов сканирования и просмотра
  • Автоматическое детектирование документа без ручного указания углов
  • Постобработка с адаптивной пороговой обработкой улучшает читаемость текста
  • Обрезка краев после перспективного преобразования удаляет артефакты
Практические рекомендации:
  • Для лучшего качества размещайте документ на контрастном фоне
  • Обеспечьте равномерное освещение для минимизации теней
  • Держите камеру перпендикулярно документу для минимизации искажений

К чек-листу


Заключение по проектам

В этих уроках мы рассмотрели два практических проекта на основе OpenCV:

  1. Виртуальная рисовальная доска - демонстрирует детектирование цветов, трекинг объектов и интерактивное рисование
  2. Сканер документов - показывает применение контурного анализа, перспективных преобразований и улучшения качества изображений

Эти проекты иллюстрируют реальное применение компьютерного зрения в повседневных задачах и предоставляют основу для дальнейшего развития навыков в этой области. Следующий урок будет посвящен третьему проекту - распознаванию номеров транспортных средств.

К чек-листу

ЧЕК-ЛИСТ ПО 2 ЧАСТИ: ПРОДВИНУТЫЕ ФУНКЦИИ И ПРОЕКТЫ OPEN CV

Детектирование цветов с помощью HSV цветового пространства

Задача
Понимаю структуру HSV цветового пространства:
  • Hue (Оттенок): диапазон 0-179 в OpenCV (0-360 в реальности)
  • Saturation (Насыщенность): диапазон 0-255
  • Value (Яркость): диапазон 0-255
Знаю преимущества HSV перед BGR для детектирования цветов
Могу конвертировать изображение из BGR в HSV с помощью cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
Понимаю, как подобрать диапазоны HSV для различных цветов:
  • Оранжевый: ~[5-19, 107-255, 0-255]
  • Фиолетовый: ~[133-159, 56-156, 0-255]
  • Зеленый: ~[57-100, 76-255, 0-255]
  • Синий: ~[105-130, 50-255, 50-255]

Создание интерактивных трекбаров для настройки параметров

Задача
Могу создать именованное окно с помощью cv2.namedWindow("TrackBars")
Умею изменять размер окна cv2.resizeWindow("TrackBars", 640, 240)
Создаю трекбары для настройки параметров с помощью cv2.createTrackbar()
Знаю, как получать текущие значения трекбаров с помощью cv2.getTrackbarPos()
Понимаю, зачем нужна пустая функция обратного вызова def empty(a): pass

Создание масок и применение цветовой фильтрации

Задача
Могу создать бинарную маску с помощью cv2.inRange(imgHSV, lower, upper)
Понимаю, как работают нижние и верхние границы в функции inRange
Умею применять маску к исходному изображению с помощью cv2.bitwise_and(img, img, mask=mask)
Знаю, как улучшить качество маски с помощью морфологических операций (эрозия + дилатация)
Могу создать генерическое решение для детектирования нескольких цветов одновременно
Понимаю, как использовать списки для хранения параметров разных цветов

Обнаружение контуров и классификация геометрических фигур

Задача
Понимаю теоретическую основу контурного анализа
Знаю различные режимы поиска контуров:
  • cv2.RETR_EXTERNAL - только внешние контуры
  • cv2.RETR_LIST - все контуры без иерархии
  • cv2.RETR_TREE - все контуры с полной иерархией
Знаю методы аппроксимации контуров:
  • cv2.CHAIN_APPROX_NONE - все точки контура
  • cv2.CHAIN_APPROX_SIMPLE - сжатие сегментов
Могу найти контуры на бинарном изображении с помощью cv2.findContours()
Умею вычислять характеристики контуров:
  • Площадь: cv2.contourArea(cnt)
  • Длину (периметр): cv2.arcLength(cnt, True)
  • Аппроксимацию многоугольником: cv2.approxPolyDP(cnt, 0.02 * peri, True)
Могу классифицировать геометрические фигуры по количеству углов:
  • 3 угла = треугольник
  • 4 угла = прямоугольник/квадрат (проверка отношения сторон)
  • >4 углов = круг
Умею рисовать ограничивающие прямоугольники: cv2.boundingRect(approx)
Знаю, как отображать текст с информацией о фигурах на изображении

Детектирование лиц с помощью каскадов Хаара

Задача
Понимаю теоретическую основу метода Виолы-Джонса
Знаю преимущества и ограничения каскадов Хаара
Могу загрузить предобученный каскад с помощью cv2.CascadeClassifier()
Умею детектировать лица на изображении с помощью detectMultiScale()
Понимаю назначение параметров функции detectMultiScale:
scaleFactor - параметр масштабирования (1.05-1.3)
minNeighbors - минимальное количество соседей для подтверждения (3-6)
minSize, maxSize - минимальный и максимальный размер объекта
Могу детектировать глаза внутри областей лиц (ROI)
Реализовал детектирование лиц в реальном времени с веб-камеры
Знаю, где найти другие предобученные каскады (глаза, улыбки, полный рост)

Виртуальная рисовальная доска (Проект 1)

Задача
Понимаю архитектуру проекта: входной модуль, модуль обработки, модуль рисования, модуль интерфейса
Реализовал детектирование нескольких цветных маркеров одновременно
Умею фильтровать контуры по площади для удаления шума (area > 1000)
Реализовал нахождение центра верхней границы объекта как точки для рисования
Создал систему хранения точек для рисования: myPoints = [] # [[x, y, colorId], ...]
Реализовал сглаживание траектории для плавного рисования
Добавил визуальную обратную связь (круги на кончиках маркеров)
Реализовал управление с клавиатуры:
  • 'c' для очистки холста
  • 'q' для выхода
Добавил зеркальное отображение для улучшения UX: cv2.flip(img, 1)
Реализовал разделение холста и изображения камеры для сохранения качества рисунка
Создал панель инструментов с выбором цветов и отображением текущих параметров

Сканер документов (Проект 2)

Задача
Понимаю теоретическую основу перспективного преобразования
Реализовал предварительную обработку изображения:
Конвертация в градации серого
Гауссово размытие для уменьшения шума
Детектирование границ Canny
Морфологические операции (дилатация и эрозия)
Реализовал поиск контуров документа с фильтрацией по площади area > 5000
Умею аппроксимировать контур четырехугольником для определения углов документа
Реализовал правильную сортировку угловых точек в порядке:
  • Верхний левый (минимальная сумма координат) 
  • Верхний правый (минимальная разница координат)
  • Нижний левый (максимальная разница координат)
  • Нижний правый (максимальная сумма координат)
Могу вычислить матрицу перспективного преобразования: cv2.getPerspectiveTransform(pts1, pts2)
Умею применить перспективное преобразование: cv2.warpPerspective(img, matrix, (w, h))
Реализовал постобработку для улучшения читаемости документа:
Адаптивная пороговая обработка
Обрезка краев для удаления артефактов
Создал интерактивный интерфейс с переключением режимов:
Режим сканирования
Режим просмотра результата
Добавил управление с клавиатуры для переключения режимов и сброса результатов

Практические навыки и оптимизация

Задача
Создал генерическое решение для детектирования цветов с возможностью расширения
Реализовал сглаживание траектории в виртуальной рисовальной доске
Оптимизировал производительность обработки изображений в реальном времени
Добавил обработку ошибок и проверку существования файлов
Реализовал адаптивные параметры для работы при разных условиях освещения
Создал визуальную обратную связь для пользователя в обоих проектах
Документировал код с комментариями для объяснения ключевых шагов
Протестировал решения на различных изображениях и сценах

Понимание ключевых концепций

Задача
Понимаю, как работает цветовое пространство HSV и почему оно эффективно для детектирования цветов
Знаю, как алгоритм Виолы-Джонса использует интегральные изображения и каскады классификаторов
Понимаю принцип работы перспективного преобразования и гомографии
Знаю, как контурный анализ помогает в распознавании форм и объектов
Понимаю важность предварительной обработки изображений для улучшения качества детектирования
Осознаю компромисс между скоростью и точностью в системах реального времени
Знаю, как правильно освобождать ресурсы после работы с видео и камерой

Общие замечания для самопроверки

Задача
Все предобученные каскады доступны в папке resources
Проверяю существование файлов перед их чтением
Обрабатываю возможные ошибки при работе с камерой и файлами
Использую понятные имена переменных и функций
Добавляю комментарии к сложным участкам кода
Тестирую проекты при разных условиях освещения
Оптимизирую параметры для конкретных сценариев использования
Соблюдаю принципы DRY (Don't Repeat Yourself) в коде
Все окна OpenCV закрываются после завершения программы
Правильно освобождаю ресурсы камеры с помощью cap.release()

Ссылки по теме

Вторник, 23 декабря 2025
OPENCV НА PYTHON | Часть 2 | БАЗОВЫЕ ЗНАНИЯ