Компьютерное зрение — Как роботы видят мир
Представь, что роботу дали фотографию и сказали: “Что здесь нарисовано?” Для нас это просто — мы видим кошку на диване. Для робота это массив из миллионов чисел (пикселей). Компьютерное зрение — это перевод с “пиксельного” языка на “человеческий”.
Что такое изображение для компьютера?
Простое изображение 3x3 пикселя в градациях серого:
# Каждый пиксель — число от 0 (чёрный) до 255 (белый)
image = [
[255, 128, 0],
[128, 0, 128],
[0, 255, 128]
]
# Визуализация:
# █▓░ (255, 128, 0)
# ▓░▓ (128, 0, 128)
# ░█▓ (0, 255, 128)
Цветное изображение — три таких матрицы: Red, Green, Blue (RGB).
OpenCV — Главный инструмент
OpenCV — как швейцарский нож для работы с изображениями. Установить просто:
pip install opencv-python
Базовые операции за 5 минут:
import cv2
import numpy as np
# 1. Загрузка изображения
image = cv2.imread('robot_view.jpg') # Читаем файл
# Если файла нет, создадим тестовое изображение
if image is None:
# Создаём изображение 400x300 синего цвета
image = np.zeros((300, 400, 3), dtype=np.uint8)
image[:, :] = (255, 0, 0) # Синий (BGR формат!)
# 2. Показываем
cv2.imshow('Что видит робот', image)
cv2.waitKey(0) # Ждём нажатия любой клавиши
cv2.destroyAllWindows()
# 3. Информация об изображении
print(f"Размер: {image.shape}") # (высота, ширина, каналы)
print(f"Тип данных: {image.dtype}")
print(f"Всего пикселей: {image.size}")
Практические задачи
1. Найти красный мяч
Самый частый сценарий: робот ищет цветной объект.
def find_red_object(image):
# 1. Конвертируем в HSV (проще работать с цветами)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 2. Диапазон красного цвета в HSV
lower_red1 = np.array([0, 100, 100]) # Красный с одной стороны
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([160, 100, 100]) # Красный с другой стороны
upper_red2 = np.array([180, 255, 255])
# 3. Создаём маски
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
red_mask = cv2.bitwise_or(mask1, mask2)
# 4. Находим контуры
contours, _ = cv2.findContours(red_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
# Берём самый большой контур
largest = max(contours, key=cv2.contourArea)
# Находим центр и радиус
((x, y), radius) = cv2.minEnclosingCircle(largest)
if radius > 10: # Игнорируем мелкие шумы
# Рисуем кружок вокруг объекта
cv2.circle(image, (int(x), int(y)), int(radius), (0, 255, 0), 2)
cv2.putText(image, "Red ball", (int(x), int(y)-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
return image, (int(x), int(y), int(radius))
return image, None
# Использование с веб-камерой
cap = cv2.VideoCapture(0) # 0 - первая камера
while True:
ret, frame = cap.read()
if not ret:
break
# Ищем красный объект
processed, ball = find_red_object(frame)
if ball:
x, y, r = ball
print(f"Мяч в ({x}, {y}), радиус {r} пикселей")
cv2.imshow('Поиск красного мяча', processed)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
2. Распознать QR-код
QR-коды — простой способ передачи информации роботу.
import cv2
from pyzbar import pyzbar
def read_qr_codes(image):
# Ищем все QR и штрих-коды
barcodes = pyzbar.decode(image)
results = []
for barcode in barcodes:
# Координаты углов
x, y, w, h = barcode.rect
# Декодируем данные
barcode_data = barcode.data.decode("utf-8")
barcode_type = barcode.type
# Рисуем рамку
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
# Подписываем
text = f"{barcode_type}: {barcode_data}"
cv2.putText(image, text, (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
results.append({
'type': barcode_type,
'data': barcode_data,
'location': (x, y, w, h)
})
return image, results
# Пример использования
image = cv2.imread('qrcode.jpg')
processed, codes = read_qr_codes(image)
for code in codes:
print(f"Найден {code['type']}: {code['data']}")
if code['data'].startswith('http'):
print(f"Это ссылка! Робот может перейти по ней")
cv2.imshow('QR-коды', processed)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. Распознать лица
def detect_faces(image):
# Загружаем предобученный классификатор
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
# Конвертируем в ч/б (работает быстрее)
gray = cv2.cvtColor(image, 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(image, (x, y), (x + w, y + h), (255, 0, 0), 2)
cv2.putText(image, "Face", (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)
return image, len(faces)
# Использование с веб-камерой
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
processed, count = detect_faces(frame)
print(f"Найдено лиц: {count}")
cv2.imshow('Распознавание лиц', processed)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
Нейросети для компьютерного зрения
OpenCV хорош для простых задач, но для сложных нужны нейросети.
YOLO (You Only Look Once) — Детекция объектов в реальном времени
import cv2
import numpy as np
# Минимальный пример с предобученной YOLO
def detect_objects_yolo(image):
# Загружаем веса и конфигурацию
net = cv2.dnn.readNet("yolov3.weights", "yolov3.cfg")
# Загружаем классы (80 объектов из COCO датасета)
with open("coco.names", "r") as f:
classes = [line.strip() for line in f.readlines()]
# Подготавливаем изображение
height, width = image.shape[:2]
blob = cv2.dnn.blobFromImage(image, 1/255, (416, 416), swapRB=True, crop=False)
# Пропускаем через сеть
net.setInput(blob)
outputs = net.forward(net.getUnconnectedOutLayersNames())
# Обрабатываем результаты
boxes = []
confidences = []
class_ids = []
for output in outputs:
for detection in output:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > 0.5: # Порог уверенности
# Координаты объекта
center_x = int(detection[0] * width)
center_y = int(detection[1] * height)
w = int(detection[2] * width)
h = int(detection[3] * height)
# Верхний левый угол
x = int(center_x - w / 2)
y = int(center_y - h / 2)
boxes.append([x, y, w, h])
confidences.append(float(confidence))
class_ids.append(class_id)
# Подавление немаксимумов (убираем дублирующиеся боксы)
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)
# Рисуем результаты
if len(indexes) > 0:
for i in indexes.flatten():
x, y, w, h = boxes[i]
label = str(classes[class_ids[i]])
confidence = confidences[i]
# Разные цвета для разных классов
color = (0, 255, 0) # Зелёный по умолчанию
if label == "person":
color = (255, 0, 0) # Синий для людей
elif label == "car":
color = (0, 0, 255) # Красный для машин
cv2.rectangle(image, (x, y), (x + w, y + h), color, 2)
cv2.putText(image, f"{label} {confidence:.2f}",
(x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
return image
# Простой пример без скачивания файлов
def simple_object_detection(image):
# Имитируем работу нейросети
# В реальности здесь была бы настоящая нейросеть
# Просто рисуем тестовые объекты
objects = [
{"label": "cat", "confidence": 0.92, "box": (50, 50, 100, 100)},
{"label": "dog", "confidence": 0.87, "box": (200, 80, 120, 120)},
{"label": "person", "confidence": 0.95, "box": (350, 60, 80, 150)}
]
for obj in objects:
label = obj["label"]
confidence = obj["confidence"]
x, y, w, h = obj["box"]
color = (0, 255, 0)
cv2.rectangle(image, (x, y), (x + w, y + h), color, 2)
cv2.putText(image, f"{label} {confidence:.2f}",
(x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
return image
Стереозрение: Получение глубины
Как два глаза помогают нам оценивать расстояние, так две камеры помогают роботу.
import cv2
import numpy as np
def estimate_depth(left_image, right_image):
"""
Оценивает глубину по двум изображениям.
Чем больше смещение (диспаритет), тем ближе объект.
"""
# Конвертируем в ч/б
gray_left = cv2.cvtColor(left_image, cv2.COLOR_BGR2GRAY)
gray_right = cv2.cvtColor(right_image, cv2.COLOR_BGR2GRAY)
# Создаём стереоматчер
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)
# Вычисляем диспаритет (разницу)
disparity = stereo.compute(gray_left, gray_right)
# Нормализуем для отображения
disparity_normalized = cv2.normalize(disparity, None, 0, 255, cv2.NORM_MINMAX)
disparity_normalized = np.uint8(disparity_normalized)
# Применяем цветовую карту для наглядности
disparity_colored = cv2.applyColorMap(disparity_normalized, cv2.COLORMAP_JET)
return disparity_colored
# Если нет двух камер, создаём тестовые изображения
def create_test_stereo_images():
# Левое изображение: белый квадрат слева
left = np.zeros((300, 400, 3), dtype=np.uint8)
left[100:200, 50:150] = (255, 255, 255) # Белый квадрат
# Правое изображение: тот же квадрат смещён вправо
right = np.zeros((300, 400, 3), dtype=np.uint8)
right[100:200, 100:200] = (255, 255, 255) # Квадрат смещён
return left, right
# Тестируем
left_img, right_img = create_test_stereo_images()
depth_map = estimate_depth(left_img, right_img)
# Показываем результат
cv2.imshow('Left camera', left_img)
cv2.imshow('Right camera', right_img)
cv2.imshow('Depth map (ближе = ярче)', depth_map)
cv2.waitKey(0)
cv2.destroyAllWindows()
Проект: Робот, который следует за человеком
Соберём всё вместе:
import cv2
import numpy as np
class PersonFollower:
def __init__(self):
# Загружаем классификатор для лиц и тел
self.face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
# Параметры ПИД-регулятора для плавного движения
self.kp = 0.1 # Пропорциональный коэффициент
self.ki = 0.01 # Интегральный
self.kd = 0.05 # Дифференциальный
self.prev_error = 0
self.integral = 0
def find_person(self, image):
"""Находит человека в кадре"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Ищем лица
faces = self.face_cascade.detectMultiScale(gray, 1.1, 5)
if len(faces) > 0:
# Берём самое большое лицо (предполагаем, что это ближайший человек)
(x, y, w, h) = max(faces, key=lambda f: f[2] * f[3])
# Центр лица
center_x = x + w // 2
center_y = y + h // 2
# Рисуем
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.circle(image, (center_x, center_y), 5, (0, 0, 255), -1)
return image, (center_x, center_y, w * h)
return image, None
def calculate_control(self, person_center, frame_width):
"""Рассчитывает управляющий сигнал для робота"""
if person_center is None:
return 0 # Не двигаемся
px, py, size = person_center
# Ошибка: насколько лицо смещено от центра кадра
error = (px - frame_width // 2) / (frame_width // 2)
# ПИД-регулятор
self.integral += error
derivative = error - self.prev_error
control = (self.kp * error +
self.ki * self.integral +
self.kd * derivative)
self.prev_error = error
# Ограничиваем выход
control = max(-1, min(1, control))
return control
def get_motor_commands(self, control):
"""Переводит управляющий сигнал в команды моторам"""
if abs(control) < 0.1: # Если почти по центру
return ("forward", 50) # Едем вперёд
if control > 0:
return ("right", int(50 + control * 50))
else:
return ("left", int(50 - control * 50))
def run(self):
"""Главный цикл"""
cap = cv2.VideoCapture(0)
print("Робот-проводник запущен!")
print("Управление:")
print(" q - выход")
print(" s - остановить/запустить")
running = True
while True:
if not running:
cv2.waitKey(100)
continue
ret, frame = cap.read()
if not ret:
break
# Находим человека
processed, person = self.find_person(frame.copy())
# Рассчитываем управление
if person:
control = self.calculate_control(person, frame.shape[1])
command, speed = self.get_motor_commands(control)
# Выводим команду
cv2.putText(processed, f"Command: {command} {speed}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
print(f"Команда: {command} со скоростью {speed}")
cv2.imshow('Person Follower', processed)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('s'):
running = not running
print(f"Робот {'остановлен' if not running else 'запущен'}")
cap.release()
cv2.destroyAllWindows()
print("Робот выключен")
# Запуск
if __name__ == "__main__":
follower = PersonFollower()
follower.run()
Что попробовать самому?
Проекты для начинающих:
- Детектор улыбок — находит улыбающиеся лица
- Счётчик людей — считает, сколько людей прошло перед камерой
- Распознаватель жестов — понимает “стоп”, “иди сюда”
- Сканер документов — выравнивает и обрезает фотографию документа
Для продвинутых:
- AR-маркеры — робот следует за ArUco-маркерами
- SLAM с камерой — построение карты по видео
- Трекинг объектов — слежение за движущимся объектом
- Распознавание действий — “человек идёт”, “сидит”, “поднимает руку”
Инструменты:
- OpenCV — основа основ
- Dlib — для распознавания лиц и поз
- MediaPipe (от Google) — для распознавания жестов и поз
- YOLO/Darknet — для детекции объектов
- TensorFlow/PyTorch — для своих нейросетей
Советы по работе с компьютерным зрением:
- Начинай с ч/б — проще, быстрее, часто достаточно
- Используй готовые модели — не учи с нуля, если не нужно
- Оптимизируй — роботы работают в реальном времени
- Тестируй при разном освещении — свет меняет всё
- Не доверяй слепо — всегда добавляй проверки и fallback’и
Помни: Компьютерное зрение — это не про идеальные условия в лаборатории, а про работу в нашем грязном, неидеальном, но очень интересном мире!
