Как работает компьютерное зрение: от пикселя до распознавания объектов
Обзорная статья о ключевых концепциях CV — фильтрации, детекции краёв, contours и базовом распознавании — с примерами на OpenCV и Python.
Содержание
Как работает компьютерное зрение: от пикселя до распознавания объектов
Когда смартфон мгновенно распознаёт лицо на фотографии, а беспилотный автомобиль определяет пешехода на дороге — за этим стоит одна и та же дисциплина: компьютерное зрение. Для стороннего наблюдателя это выглядит как магия. Для инженера — это цепочка конкретных математических операций над массивами чисел.
В этой статье мы разберём, как именно машина «видит»: от представления изображения в памяти до детекции объектов. Никакой магии — только механика.
Что такое изображение с точки зрения машины
Прежде чем говорить об алгоритмах, нужно понять, с чем они работают.
Цифровое изображение — это двумерный массив пикселей. Каждый пиксель хранит числовое значение интенсивности. В случае градаций серого это одно число от 0 (чёрный) до 255 (белый). Цветное изображение в формате RGB — это три таких массива, наложенных друг на друга: красный канал, зелёный и синий.
В Python с помощью OpenCV это выглядит так:
import cv2
import numpy as np
# Загрузка изображения
img = cv2.imread('photo.jpg')
# Форма массива: (высота, ширина, количество каналов)
print(img.shape) # например, (480, 640, 3)
# Значение конкретного пикселя (строка 100, столбец 200)
pixel = img[100, 200]
print(pixel) # например, [120 85 60] — BGR-значения
Важная деталь: OpenCV по умолчанию читает изображения в формате BGR, а не RGB. Это историческое решение, которое регулярно сбивает с толку новичков при визуализации через Matplotlib.
Понимание изображения как числового массива — это фундамент. Все дальнейшие операции компьютерного зрения — это, по сути, математические преобразования этого массива.
Предобработка: зачем нужна фильтрация
Реальные изображения редко бывают идеальными. Шум от матрицы камеры, неравномерное освещение, артефакты сжатия — всё это мешает алгоритмам работать корректно. Поэтому перед анализом изображение, как правило, проходит предобработку.
Размытие и сглаживание
Самый распространённый инструмент предобработки — свёртка (convolution). Идея проста: мы берём небольшую матрицу-ядро (kernel) и «прокатываем» её по изображению, заменяя каждый пиксель взвешенной суммой его соседей.
Гауссово размытие — один из стандартных методов подавления шума:
# Гауссово размытие с ядром 5x5
blurred = cv2.GaussianBlur(img, (5, 5), sigmaX=0)
Размер ядра и параметр sigma определяют степень размытия. Чем больше ядро — тем сильнее сглаживание, но и тем больше теряется деталей.
Медианное размытие работает иначе: каждый пиксель заменяется медианным значением из окрестности. Оно особенно эффективно против «соль и перец» — случайных белых и чёрных точек:
median_blurred = cv2.medianBlur(img, 5)
Преобразование в оттенки серого
Многие алгоритмы работают с одноканальными изображениями. Перевод в градации серого — стандартный шаг:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
Это не просто «усреднение» каналов. OpenCV использует взвешенную формулу, которая учитывает восприятие яркости человеческим глазом: зелёный канал вносит наибольший вклад, синий — наименьший.
Детекция краёв: где заканчивается один объект и начинается другой
Края — это места резкого изменения интенсивности пикселей. Именно они несут основную структурную информацию об объектах на изображении.
Оператор Собеля
Оператор Собеля вычисляет градиент яркости по горизонтали и вертикали. Там, где градиент велик — есть граница.
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Градиент по X и Y
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
# Итоговая величина градиента
magnitude = cv2.magnitude(sobel_x, sobel_y)
Алгоритм Кэнни
Canny Edge Detection — один из самых известных алгоритмов в компьютерном зрении. Он был предложен Джоном Кэнни в 1986 году и до сих пор остаётся стандартом де-факто для многих задач.
Алгоритм работает в несколько этапов:
- Гауссово размытие для подавления шума
- Вычисление градиента (на основе оператора Собеля)
- Подавление немаксимумов — оставляем только «гребни» градиента
- Двойная пороговая фильтрация — сильные края, слабые края и шум
- Трассировка краёв по гистерезису — слабые края сохраняются, только если соединены с сильными
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Два порога: нижний и верхний
edges = cv2.Canny(blurred, threshold1=50, threshold2=150)
Выбор порогов — это искусство, которое приходит с опытом. Распространённое эмпирическое правило: верхний порог примерно в 2–3 раза больше нижнего. Но для каждой конкретной задачи значения нужно подбирать.
Контуры: от краёв к геометрии объектов
Детекция краёв даёт нам карту границ. Следующий шаг — объединить эти границы в связные контуры, которые описывают форму объектов.
Поиск и отрисовка контуров
# Бинаризация изображения — необходимое условие для findContours
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# Поиск контуров
contours, hierarchy = cv2.findContours(
thresh,
cv2.RETR_EXTERNAL, # только внешние контуры
cv2.CHAIN_APPROX_SIMPLE # сжатое представление
)
# Отрисовка всех контуров
output = img.copy()
cv2.drawContours(output, contours, -1, (0, 255, 0), 2)
Параметр cv2.RETR_EXTERNAL возвращает только внешние контуры, игнорируя вложенные. Если нужна иерархия — используйте cv2.RETR_TREE.
Анализ формы через контуры
Контуры — это не просто красивые линии. Из них можно извлечь полезную геометрическую информацию:
for cnt in contours:
# Площадь
area = cv2.contourArea(cnt)
# Периметр
perimeter = cv2.arcLength(cnt, closed=True)
# Ограничивающий прямоугольник
x, y, w, h = cv2.boundingRect(cnt)
# Аппроксимация формы полигоном
epsilon = 0.02 * perimeter
approx = cv2.approxPolyDP(cnt, epsilon, closed=True)
# Если 4 вершины — скорее всего прямоугольник
if len(approx) == 4:
cv2.rectangle(output, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.approxPolyDP реализует алгоритм Дугласа-Пекера: он упрощает контур до полигона с заданной точностью. Это мощный инструмент для классификации форм — треугольники, прямоугольники, многоугольники.
Пороговая сегментация и морфологические операции
Прежде чем искать контуры, изображение нужно бинаризовать — разделить пиксели на «объект» и «фон».
Адаптивная пороговая обработка
Простая пороговая обработка с фиксированным значением плохо работает при неравномерном освещении. Адаптивный порог вычисляет пороговое значение локально, для каждого небольшого региона:
# Адаптивный порог на основе среднего по окрестности
adaptive_thresh = cv2.adaptiveThreshold(
gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
blockSize=11,
C=2
)
Морфологические операции
После бинаризации изображение часто содержит мелкие артефакты. Морфологические операции позволяют их устранить:
kernel = np.ones((5, 5), np.uint8)
# Эрозия — уменьшает белые области, убирает мелкий шум
eroded = cv2.erode(thresh, kernel, iterations=1)
# Дилатация — расширяет белые области, заполняет пробелы
dilated = cv2.dilate(thresh, kernel, iterations=1)
# Opening = эрозия + дилатация (убирает шум)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# Closing = дилатация + эрозия (заполняет дыры)
closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
Базовое распознавание объектов: каскады Хаара
Один из классических подходов к детекции объектов — метод каскадных классификаторов Виолы-Джонса, предложенный в 2001 году. Он основан на признаках Хаара и до сих пор используется для быстрой детекции лиц в реальном времени.
OpenCV поставляется с набором предобученных каскадов:
# Загрузка предобученного каскада для детекции лиц
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Детекция лиц
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1, # масштаб пирамиды изображений
minNeighbors=5, # минимум соседних детекций для подтверждения
minSize=(30, 30)
)
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
Параметр scaleFactor определяет, насколько уменьшается изображение на каждом шаге пирамиды. Значение 1.1 означает уменьшение на 10% — это точнее, но медленнее. minNeighbors контролирует количество ложных срабатываний: чем выше значение, тем меньше ложных детекций, но и тем больше риск пропустить реальный объект.
Каскады Хаара хорошо работают для фронтальных лиц при хорошем освещении. Для более сложных сценариев — повороты, частичное перекрытие, разное освещение — нужны современные подходы на основе глубокого обучения: YOLO, SSD, Faster R-CNN.
Практический пайплайн: собираем всё вместе
Рассмотрим типичный пайплайн для подсчёта объектов на изображении — например, монет или деталей на конвейере:
import cv2
import numpy as np
def count_objects(image_path):
# 1. Загрузка и предобработка
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (11, 11), 0)
# 2. Бинаризация
_, thresh = cv2.threshold(
blurred, 0, 255,
cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU # метод Оцу
)
# 3. Морфологическая очистка
kernel = np.ones((3, 3), np.uint8)
cleaned = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# 4. Поиск контуров
contours, _ = cv2.findContours(
cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
# 5. Фильтрация по площади (убираем мелкий шум)
min_area = 500
valid_contours = [c for c in contours if cv2.contourArea(c) > min_area]
# 6. Визуализация
result = img.copy()
cv2.drawContours(result, valid_contours, -1, (0, 255, 0), 2)
print(f"Найдено объектов: {len(valid_contours)}")
return result, len(valid_contours)
Метод Оцу (cv2.THRESH_OTSU) автоматически находит оптимальное пороговое значение на основе гистограммы изображения — это избавляет от необходимости подбирать порог вручную.
Именно такие пайплайны — основа промышленного компьютерного зрения. Если хочется разобраться в них системно, а не собирать знания по кускам из документации, стоит обратить внимание на структурированные курсы. Например, курс по OpenCV на платформе «$ sudo teach IT» охватывает именно этот практический пласт: от базовых операций с изображениями до построения полноценных CV-пайплайнов.
Типичные ошибки и подводные камни
За годы работы с OpenCV у разработчиков накапливается список граблей, на которые наступают снова и снова.
BGR вместо RGB. OpenCV читает изображения в BGR. Если передать такое изображение в Matplotlib или PIL без конвертации — цвета будут искажены. Решение: cv2.cvtColor(img, cv2.COLOR_BGR2RGB) перед отображением.
Тип данных после операций. После cv2.Sobel с флагом cv2.CV_64F результат содержит отрицательные значения и числа с плавающей точкой. Прямая конвертация в uint8 обрежет отрицательные значения. Правильный подход — взять абсолютное значение и нормализовать.
Неправильный порядок аргументов. В OpenCV координаты часто задаются как (x, y), а индексы массива numpy — как [row, col], то есть [y, x]. Путаница здесь — источник трудноуловимых багов.
Размер ядра должен быть нечётным. Ядра свёртки в OpenCV должны иметь нечётные размеры (3, 5, 7...). Чётное значение вызовет ошибку.
Контуры ищутся только на бинарных изображениях. Передача в findContours цветного или серого изображения без предварительной бинаризации даст непредсказуемый результат.
Куда двигаться дальше
Классическое компьютерное зрение, которое мы разобрали — фильтрация, детекция краёв, контуры, каскадные классификаторы — это фундамент. Он не устарел: эти методы работают быстро, требуют мало ресурсов и хорошо интерпретируемы.
Но современные задачи — распознавание объектов в сложных условиях, семантическая сегментация, трекинг в видео — решаются с помощью глубокого обучения. Свёрточные нейронные сети (CNN) научились автоматически извлекать признаки, которые раньше приходилось задавать вручную.
Путь от пикселя до распознавания объектов сегодня выглядит так: классический CV даёт понимание основ и инструменты для быстрых решений, а нейронные сети берут на себя сложные сценарии. Опытный инженер умеет работать с обоими подходами и выбирать нужный в зависимости от задачи.
Итог
Компьютерное зрение — это не магия, а последовательное применение математических операций к числовым массивам. Свёртка сглаживает шум. Градиент находит границы. Контуры описывают форму. Классификатор принимает решение.
Каждый из этих шагов понятен и воспроизводим. OpenCV предоставляет готовые реализации, но понимание того, что происходит «под капотом», принципиально важно — иначе настройка параметров превращается в гадание на кофейной гуще.
Если вы хотите выстроить эти знания системно — от базовых операций до практических проектов — курс по OpenCV может стать хорошей отправной точкой. Но даже без него: экспериментируйте с кодом из этой статьи, меняйте параметры, смотрите на результат. Компьютерное зрение лучше всего изучается руками.
Комментарии
Пока нет комментариев