OPENCV НА PYTHON | Часть 2 | БАЗОВЫЕ ЗНАНИЯ
Урок 8: Обнаружение цветов с помощью HSV цветового пространства ↑
8.1 Теоретическая основа HSV цветового пространства
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) был первым, обеспечивающим детектирование лиц в реальном времени
Основные компоненты:
- Интегральное изображение - быстрое вычисление суммы пикселей в прямоугольнике
- Каскад классификаторов - последовательность простых классификаторов
- Признаки Хаара - простые прямоугольные признаки для описания локальных особенностей
- Алгоритм 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 Постановка задачи и архитектура проекта
Требования к системе:
- Работа в реальном времени с веб-камерой
- Детектирование нескольких цветов одновременно
- Плавное рисование с минимальной задержкой
- Возможность стирания и очистки холста
- Интуитивный интерфейс пользователя
Архитектура проекта:
- Входной модуль: Захват видео с веб-камеры
- Модуль обработки: Детектирование цветов и позиций маркеров
- Модуль рисования: Управление точками и отрисовка на холсте
- Модуль интерфейса: Отображение результатов и управление
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:
- Виртуальная рисовальная доска - демонстрирует детектирование цветов, трекинг объектов и интерактивное рисование
- Сканер документов - показывает применение контурного анализа, перспективных преобразований и улучшения качества изображений
Эти проекты иллюстрируют реальное применение компьютерного зрения в повседневных задачах и предоставляют основу для дальнейшего развития навыков в этой области. Следующий урок будет посвящен третьему проекту - распознаванию номеров транспортных средств.
К чек-листуЧЕК-ЛИСТ ПО 2 ЧАСТИ: ПРОДВИНУТЫЕ ФУНКЦИИ И ПРОЕКТЫ OPEN CV
Детектирование цветов с помощью HSV цветового пространства
| ✓ | Задача |
|---|---|
| Понимаю структуру HSV цветового пространства: | |
|
|
| Знаю преимущества HSV перед BGR для детектирования цветов | |
Могу конвертировать изображение из BGR в HSV с помощью cv2.cvtColor(img, cv2.COLOR_BGR2HSV) |
|
| Понимаю, как подобрать диапазоны HSV для различных цветов: | |
|
Создание интерактивных трекбаров для настройки параметров
| ✓ | Задача |
|---|---|
Могу создать именованное окно с помощью 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.findContours() |
|
| Умею вычислять характеристики контуров: | |
|
|
| Могу классифицировать геометрические фигуры по количеству углов: | |
|
|
Умею рисовать ограничивающие прямоугольники: 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], ...] |
|
| Реализовал сглаживание траектории для плавного рисования | |
| Добавил визуальную обратную связь (круги на кончиках маркеров) | |
| Реализовал управление с клавиатуры: | |
|
|
Добавил зеркальное отображение для улучшения 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() |

