Перейти к содержанию

Блок 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 так важен

Во многих задачах изображение сначала упрощают:

  1. было обычное цветное изображение;
  2. перевели в grayscale;
  3. сделали binary;
  4. теперь объект уже легко отделяется от фона.

То есть идея такая:

упростить картинку, чтобы дальше работать не с “фото”, а почти с логической маской.


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. Смысл всего конвейера

Очень важно увидеть общую схему:

  1. загрузили изображение;
  2. перевели в удобный формат;
  3. упростили изображение;
  4. построили маску;
  5. измерили нужные признаки.

Именно так устроены многие практические задачи по обработке изображений.


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)