Блок 2. cv2 как работа с массивом¶
Для чего нужен этот блок¶
В первом блоке ты познакомился с изображением как с набором пикселей через PIL.
Теперь пора перейти к библиотеке cv2 и сделать следующий важный шаг:
понять, что изображение в cv2 — это не просто картинка, а обычный массив чисел, с которым можно работать алгоритмически.
После изучения этого блока ты должен уметь:
- загружать изображение через
cv2; - понимать, что такое массив изображения;
- узнавать размер изображения и число каналов;
- различать
BGRиRGB; - получать доступ к отдельным пикселям и областям;
- переводить изображение в оттенки серого;
- делать бинаризацию по порогу;
- строить простые маски;
- выделять объекты по яркости или по цвету;
- сохранять результаты обработки.
1. Что такое изображение в cv2¶
В cv2 изображение обычно хранится как массив NumPy.
Это значит, что картинка — это просто таблица чисел.
Если изображение цветное, то обычно у него три канала:
- синий;
- зеленый;
- красный.
То есть один пиксель может выглядеть так:
[255, 0, 0]
Но в cv2 это означает не красный цвет, а синий, потому что порядок каналов здесь не RGB, а BGR.
Это одна из самых важных вещей во всем блоке.
2. Установка библиотек¶
Для работы нужны:
opencv-python;numpy.
Установка:
pip install opencv-python numpy
Подключение в программе:
import cv2
import numpy as np
3. Загрузка изображения¶
import cv2
img = cv2.imread("picture.png")
print(img)
Что делает cv2.imread¶
- открывает файл изображения;
- возвращает массив NumPy;
- если файл не найден, может вернуть
None.
Поэтому полезно иногда проверять:
import cv2
img = cv2.imread("picture.png")
if img is None:
print("Не удалось открыть изображение")
else:
print("Изображение загружено")
4. Форма массива изображения¶
В cv2 очень важно уметь смотреть на shape.
import cv2
img = cv2.imread("picture.png")
print(img.shape)
Например, можно получить:
(600, 800, 3)
Это означает:
600— высота;800— ширина;3— число каналов.
Важно¶
В PIL ты чаще работал с (width, height).
В cv2 и NumPy порядок другой:
(height, width, channels)
Это надо запомнить очень хорошо.
5. Получение высоты и ширины¶
import cv2
img = cv2.imread("picture.png")
height, width, channels = img.shape
print("Высота:", height)
print("Ширина:", width)
print("Каналов:", channels)
Если изображение уже серое, то shape может содержать только два числа:
(height, width)
6. Пиксель в cv2¶
Можно получить значение конкретного пикселя.
import cv2
img = cv2.imread("picture.png")
print(img[20, 10])
Что важно понять¶
Здесь порядок такой:
img[y, x]
То есть сначала строка, потом столбец.
Это очень частая ошибка новичка.
Если изображение цветное, ответ может быть таким:
[120 200 15]
Это значит:
- синий =
120; - зеленый =
200; - красный =
15.
То есть это формат BGR.
7. Изменение пикселя¶
import cv2
img = cv2.imread("picture.png")
img[20, 10] = [0, 0, 255]
cv2.imwrite("result.png", img)
Что произошло¶
Пиксель с координатами (x=10, y=20) стал красным.
Почему красный?
Потому что в cv2 порядок каналов такой:
[B, G, R]
Значит:
[0, 0, 255]
— это красный цвет.
8. BGR и RGB¶
Это одна из самых частых причин ошибок.
В PIL¶
Обычно цвета хранятся как:
(R, G, B)
В cv2¶
Обычно цвета хранятся как:
(B, G, R)
Поэтому:
- в
PILкрасный —(255, 0, 0); - в
cv2красный —[0, 0, 255].
9. Как взять отдельные каналы¶
Можно отдельно посмотреть синий, зеленый и красный каналы.
import cv2
img = cv2.imread("picture.png")
blue = img[:, :, 0]
green = img[:, :, 1]
red = img[:, :, 2]
print(blue.shape)
print(green.shape)
print(red.shape)
Что это значит¶
:по первой оси — все строки;:по второй оси — все столбцы;0,1,2— номер канала.
Это уже очень похоже на работу с обычными массивами.
10. Область изображения¶
Можно взять не весь массив, а только его часть.
import cv2
img = cv2.imread("picture.png")
fragment = img[50:200, 100:300]
cv2.imwrite("fragment.png", fragment)
Что это значит для нас¶
Здесь берется прямоугольная область:
yот50до199;xот100до299.
Порядок всегда такой:
img[y1:y2, x1:x2]
Это тоже очень важно.
11. Закрашивание области¶
Можно менять сразу целый кусок изображения.
import cv2
img = cv2.imread("picture.png")
img[0:100, 0:100] = [0, 0, 255]
cv2.imwrite("red_corner.png", img)
Теперь квадрат 100 x 100 в левом верхнем углу будет красным.
12. Перевод в оттенки серого¶
Очень часто в задачах сначала делают изображение серым.
Почему это полезно:
- вместо трех каналов остается один;
- с таким изображением проще работать;
- многие алгоритмы используют именно grayscale.
import cv2
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite("gray.png", gray)
Что происходит¶
cv2.cvtColor(...)меняет цветовое пространство;cv2.COLOR_BGR2GRAYозначает перевод изBGRв grayscale.
13. Как выглядит серое изображение¶
Пусть у серого изображения размер 600 x 800.
Тогда:
print(gray.shape)
даст:
(600, 800)
А отдельный пиксель будет уже одним числом:
print(gray[20, 10])
Например:
137
Это яркость пикселя.
14. Бинаризация по порогу¶
После grayscale часто делают бинаризацию.
Идея очень простая:
- если пиксель ярче порога, делаем его белым;
- иначе делаем его черным.
Например:
- все пиксели больше
127становятся255; - остальные становятся
0.
import cv2
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv2.imwrite("binary.png", binary)
Что означают параметры¶
cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
gray— входное изображение;127— порог;255— значение для белого;cv2.THRESH_BINARY— режим бинаризации.
15. Как читать результат threshold¶
Функция возвращает два значения:
ret, binary = cv2.threshold(...)
Обычно первое значение не очень нужно в простых задачах, поэтому часто пишут так:
_, binary = cv2.threshold(...)
Переменная binary — это уже готовое черно-белое изображение.
16. Обратная бинаризация¶
Иногда нужно наоборот:
- темные пиксели сделать белыми;
- светлые — черными.
import cv2
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary_inv = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
cv2.imwrite("binary_inv.png", binary_inv)
Это особенно полезно, если объект темный, а фон светлый.
17. Почему threshold так важен¶
Во многих задачах изображение сначала упрощают:
- было обычное цветное изображение;
- перевели в grayscale;
- сделали binary;
- теперь объект уже легко отделяется от фона.
То есть идея такая:
упростить картинку, чтобы дальше работать не с “фото”, а почти с логической маской.
18. Что такое маска¶
Маска — это изображение, где нужные пиксели отмечены, а остальные нет.
Обычно:
- белое (
255) — нужная область; - черное (
0) — все остальное.
Например, после пороговой обработки бинарное изображение уже можно использовать как маску.
19. Маска по яркости¶
Пусть мы хотим оставить только светлые пиксели.
import cv2
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, mask = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
cv2.imwrite("mask.png", mask)
Теперь в mask белыми будут только очень светлые области.
20. Применение маски¶
Можно оставить только те части исходного изображения, которые попали в маску.
import cv2
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, mask = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
result = cv2.bitwise_and(img, img, mask=mask)
cv2.imwrite("bright_parts.png", result)
Идея¶
maskговорит, какие пиксели оставить;bitwise_andприменяет эту маску к исходному изображению.
21. Маска по цвету¶
Иногда объект проще искать не по яркости, а по цвету.
Для этого часто используют cv2.inRange(...).
Например, можно выделять пиксели, у которых значения каналов лежат в заданных пределах.
import cv2
import numpy as np
img = cv2.imread("picture.png")
lower = np.array([0, 0, 150])
upper = np.array([100, 100, 255])
mask = cv2.inRange(img, lower, upper)
cv2.imwrite("red_mask.png", mask)
Что это теперь значит¶
mask будет белой там, где пиксель попал в диапазон:
Bот0до100;Gот0до100;Rот150до255.
То есть мы примерно ищем красные области.
22. Почему для цвета часто используют не BGR, а HSV¶
В BGR иногда неудобно искать цвет, потому что диапазоны зависят от освещения.
Поэтому часто изображение сначала переводят в HSV.
HSV удобен тем, что:
H— оттенок;S— насыщенность;V— яркость.
Для поиска цветных объектов это часто лучше.
23. Перевод в HSV¶
import cv2
img = cv2.imread("picture.png")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
print(hsv.shape)
Теперь можно строить маски уже по HSV.
24. Маска по цвету в HSV¶
Пример для красных оттенков:
import cv2
import numpy as np
img = cv2.imread("picture.png")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower = np.array([0, 100, 100])
upper = np.array([10, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
cv2.imwrite("hsv_red_mask.png", mask)
Замечание¶
Красный цвет в HSV часто нужно искать в двух диапазонах, потому что он находится на краю шкалы оттенков. Но для первого знакомства достаточно понять саму идею маски по диапазону.
25. Изменение размера изображения¶
Как и в PIL, размер можно менять.
import cv2
img = cv2.imread("picture.png")
small = cv2.resize(img, (200, 150))
cv2.imwrite("small.png", small)
Очень важно¶
Здесь размер задается как:
(width, height)
Это стоит помнить отдельно, потому что shape у массива идет как (height, width, channels), а resize принимает (width, height).
26. Подсчет белых пикселей¶
После бинаризации можно считать простые признаки.
Например, число белых пикселей.
import cv2
import numpy as np
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
count_white = np.sum(binary == 255)
print(count_white)
Это уже очень похоже на олимпиадную задачу: выделить область и измерить ее.
27. Подсчет черных пикселей¶
import cv2
import numpy as np
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
count_black = np.sum(binary == 0)
print(count_black)
28. Смысл всего конвейера¶
Очень важно увидеть общую схему:
- загрузили изображение;
- перевели в удобный формат;
- упростили изображение;
- построили маску;
- измерили нужные признаки.
Именно так устроены многие практические задачи по обработке изображений.
29. Частые ошибки новичка¶
1. Перепутаны x и y¶
В cv2 доступ к пикселю такой:
img[y, x]
а не img[x, y].
2. Перепутаны RGB и BGR¶
В cv2 порядок каналов — BGR.
3. Путаница с shape¶
У массива изображения:
(height, width, channels)
4. Путаница с resize¶
cv2.resize принимает размер как:
(width, height)
5. Забыта проверка imread¶
Если путь неверный, img может быть None.
6. Попытка делать threshold сразу по цветному изображению¶
Обычно сначала нужно перевести изображение в grayscale.
30. Минимум, который нужно запомнить¶
Подключение:
import cv2
import numpy as np
Загрузка:
img = cv2.imread("picture.png")
Форма массива:
print(img.shape)
Пиксель:
value = img[y, x]
Изменить пиксель:
img[y, x] = [0, 0, 255]
Фрагмент:
part = img[y1:y2, x1:x2]
Перевод в серый:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
Бинаризация:
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
Обратная бинаризация:
_, binary_inv = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
Маска по диапазону:
mask = cv2.inRange(img, lower, upper)
Применение маски:
result = cv2.bitwise_and(img, img, mask=mask)
Перевод в HSV:
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
Сохранение:
cv2.imwrite("result.png", img)
31. Готовые примеры¶
Пример 1. Открыть изображение и вывести его форму¶
import cv2
img = cv2.imread("picture.png")
print(img.shape)
Пример 2. Узнать значение пикселя¶
import cv2
img = cv2.imread("picture.png")
print(img[20, 10])
Пример 3. Сделать один пиксель красным¶
import cv2
img = cv2.imread("picture.png")
img[20, 10] = [0, 0, 255]
cv2.imwrite("one_red_pixel.png", img)
Пример 4. Вырезать фрагмент¶
import cv2
img = cv2.imread("picture.png")
fragment = img[50:200, 100:300]
cv2.imwrite("fragment.png", fragment)
Пример 5. Перевести изображение в серый¶
import cv2
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite("gray.png", gray)
Пример 6. Сделать бинаризацию¶
import cv2
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv2.imwrite("binary.png", binary)
Пример 7. Оставить только светлые области¶
import cv2
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, mask = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
result = cv2.bitwise_and(img, img, mask=mask)
cv2.imwrite("bright_parts.png", result)
Пример 8. Найти красные области по диапазону BGR¶
import cv2
import numpy as np
img = cv2.imread("picture.png")
lower = np.array([0, 0, 150])
upper = np.array([100, 100, 255])
mask = cv2.inRange(img, lower, upper)
cv2.imwrite("red_mask.png", mask)
Пример 9. Изменить размер изображения¶
import cv2
img = cv2.imread("picture.png")
small = cv2.resize(img, (200, 150))
cv2.imwrite("small.png", small)
Пример 10. Посчитать белые пиксели после threshold¶
import cv2
import numpy as np
img = cv2.imread("picture.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
count_white = np.sum(binary == 255)
print(count_white)
32. Практические задания¶
Задание 1¶
Открой изображение и выведи:
- высоту;
- ширину;
- число каналов.
Задание 2¶
Сделай красным квадрат 100 x 100 в левом верхнем углу.
Задание 3¶
Вырежи центральную часть изображения и сохрани ее.
Задание 4¶
Переведи изображение в оттенки серого.
Задание 5¶
Сделай бинаризацию по порогу 127.
Задание 6¶
Посчитай, сколько белых пикселей получилось после бинаризации.
Задание 7¶
Построй маску по яркости, оставив только очень светлые области.
Задание 8¶
Попробуй выделить красные области через cv2.inRange(...).
33. Почему это важно для олимпиадных задач¶
Во многих задачах по компьютерному зрению и обработке изображений нужно не просто “посмотреть на картинку”, а превратить ее в данные, с которыми удобно работать алгоритмами.
cv2 как раз дает для этого хороший инструмент.
Типичный путь такой:
- загрузить изображение;
- упростить его;
- выделить нужную область;
- посчитать признаки;
- принять решение по результату.
Это и есть основа многих прикладных задач.
34. Итог блока¶
После этого блока изображение в cv2 должно восприниматься как массив чисел, а не просто как картинка.
Ты должен начать уверенно понимать:
- где у изображения высота и ширина;
- как обратиться к пикселю;
- почему
BGRотличается отRGB; - как сделать grayscale;
- как построить binary;
- как использовать маску для выделения нужного объекта.
35. Базовый шаблон¶
import cv2
import numpy as np
img = cv2.imread("picture.png")
print(img.shape)
value = img[y, x]
img[y, x] = [0, 0, 255]
part = img[y1:y2, x1:x2]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
_, binary_inv = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
lower = np.array([0, 0, 150])
upper = np.array([100, 100, 255])
mask = cv2.inRange(img, lower, upper)
result = cv2.bitwise_and(img, img, mask=mask)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
small = cv2.resize(img, (200, 150))
cv2.imwrite("result.png", img)