Судя по количеству закладок на первой части, работа моя  —  не зряшная.
В прошлый раз разбирали скучное открывание-закрывание картинки, в этот раз засунем в неё руки поглубже:

  • Доступ к пикселям и работа с ними.

  • Масштабирование картинки.

  • Обрезка.

  • Отражение.

import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

Первой картинкой пойдут уже знакомые шашечки:

"checkerboard_18x18.png"
"checkerboard_18x18.png"

Доступ к отдельным пикселям

Изображение в OpenCV — матрица numpy, а значит, для доступа к пикселю будем использовать нотацию матриц: [r, c]. Первое значение  — строка, второе  — колонка. Не забывайте о том, что индексация начинается с нуля!

Для доступа к самому первому пикселю обратимся к элементу матрицы (изображения, то бишь) с индексами 0 и 0:

# Читаем картинку как чб
cb_img = cv2.imread("checkerboard_18x18.png",0)
# Выводим массив, представляющий картинку
print(cb_img)
# Выводим значение самого первого пикселя (верх-лево)
print(cb_img[0,0])
# Выводим значение первого пикселя слева от чёрной зоны
print(cb_img[0,6])

С доступом разобрались, поиграем со значениями.

Изменение пикселей

Просто переназначаем пиксель:

cb_img[0,0] = 255

Что-то похожее мы сделали в прошлый раз с каналами.

Продублируем картинку, и накидаем в неё серых пикселей:

cb_img_copy = cb_img.copy()	# копируем загруженное изображение
cb_img_copy[2,2] = 200			# пиксель в ячейке [2,2] будет равен 200
cb_img_copy[2,3] = 200			# и так далее
cb_img_copy[3,2] = 200
cb_img_copy[3,3] = 200

# То же самое другими словами:
# cb_img_copy[2:3,2:3] = 200

plt.imshow(cb_img_copy, cmap='gray')
plt.show()
print(cb_img_copy)

Обрезка картинок

Можно воспринимать кроп как задачу "из пачки пикселей берём только эти несколько".
Загрузим новозеландскую лодку и потренируемся на ней:

"New_Zealand_Boat.jpg"
"New_Zealand_Boat.jpg"
img_NZ_bgr = cv2.imread("New_Zealand_Boat.jpg",cv2.IMREAD_COLOR)
# или так:
# img_NZ_bgr = cv2.imread("New_Zealand_Boat.jpg",1)
# img_NZ_bgr = cv2.imread("New_Zealand_Boat.jpg")

img_NZ_rgb = img_NZ_bgr[:,:,::-1]
# или так:
# img_NZ_rgb = cv2.cvtColor(img_NZ_bgr, cv2.COLOR_BGR2RGB)
# или разбить на каналы и пересобрать в правильном порядке :)

plt.imshow(img_NZ_rgb)
plt.show()

Вырежем серединку:

# кропнутый регион = область загруженной картинки
# c 200 по 400 строку (или Y, если хотите)
# и 300 по 600 колонку (или X, если хотите)
cropped_region = img_NZ_rgb[200:400, 300:600]

plt.imshow(cropped_region)
plt.show()

Масштабирование изображений

Функция resize() отресайзит картинку в больший или меньший размер. А регулируется это всё аргументами src,dsize (обязательные),fx, fy (факультативные).

cv2.resize() — синтаксис и аргументы

dst = resize( src, dsize[, dst[, fx[, fy[, interpolation]]]] )

dst — изображение на выходе. Размер картинки будет равен dsize (если он ненулевой), или посчитан через src.size(), fx, fy.
Тип данных будет тем же, что и в оригинальной картинке.

src — понятно, сама картинка, требующая вмешательства. Обязательный аргумент.
dsize — необходимый размер, обязательный аргумент.
fx — коэффициент масштаба горизонтальный.
fy
— коэффициент масштаба вертикальный.

Чуть подробнее о том, что тут происходит

В общем: в dsize кладётся кортеж с натуральными числами, две штуки: (500, 500). Это размер, в который картинка отмасштабируется.
Можно воспользоваться вместо этого коэффициентами масштаба, тогда вместо dsize надо впечатать None.
Коэффициенты масштаба — fx и fy — берут оригинальную картинку, и растягивают/стягивают её пропорционально.

dsize — имеет приоритет: конструкция resize(src, dsize=(100, 100),fx=20, fy=20) выдаст картинку 100×100 пикселей.

Подробнее про resize(): ссылка на офф. документацию

Первый вариант масштабирования: коэффициенты масштаба

Увеличим кропнутую лодку в два раза:

resized_cropped_region_2x = cv2.resize(cropped_region,None,fx=2, fy=2)
plt.imshow(resized_cropped_region_2x)
plt.show()

Второй вариант масштабирования: сразу укажем нужные размеры

desired_width = 100  # желаемая ширина
desired_height = 200  # желаемая высота
dim = (desired_width, desired_height)  # размер в итоге

# Масштабируем картинку
resized_cropped_region = cv2.resize(cropped_region,
                                    dsize = dim,
                                    interpolation = cv2.INTER_AREA)

# Или так:
# resized_cropped_region = cv2.resize(cropped_region,
#	                                   dsize = (100, 200),
#	                                   interpolation = cv2.INTER_AREA)

plt.imshow(resized_cropped_region)
plt.show()

Масштабирование с сохранением пропорций

За основу возьмём вторую методу, но отталкиваться будем от желаемой ширины.
Немного несложной математики:

# Используем 'dsize'

desired_width = 100  # желаемая ширина

# соотношение сторон: ширина, делённая на ширину оригинала
aspect_ratio = desired_width / cropped_region.shape[1]

# желаемая высота: высота, умноженная на соотношение сторон
desired_height = int(cropped_region.shape[0] * aspect_ratio)

dim = (desired_width, desired_height)  # итоговые размеры

# Масштабируем картинку
resized_cropped_region = cv2.resize(cropped_region, dsize=dim, interpolation=cv2.INTER_AREA)
plt.imshow(resized_cropped_region)
plt.show()

Сохранимся-с!

# Приводим картинку к RGB
resized_cropped_region_2x = resized_cropped_region_2x[:,:,::-1]

# Сохраняем картинку
cv2.imwrite("resized_cropped_region_2x.png", resized_cropped_region_2x)

# Посмотрим на сокранённую картинку (тут-то нам и пригодится подгруженный PIL)
im = Image.open('resized_cropped_region_2x.png')
im.show()

Отражение картинки

Происходит с помощью функции flip().

dst = cv.flip( src, flipCode )

src — понятно, сама картинка, требующая вмешательства. Обязательный аргумент.
flipCode — флаг, объясняющий функции, как конкретно мы хотим картинку отразить.

Подробнее про flip(): ссылка на офф. документацию

img_NZ_rgb_flipped_horz = cv2.flip(img_NZ_rgb, 1)
img_NZ_rgb_flipped_vert = cv2.flip(img_NZ_rgb, 0)
img_NZ_rgb_flipped_both = cv2.flip(img_NZ_rgb, -1)

plt.figure(figsize=[18,5])
plt.subplot(141);plt.imshow(img_NZ_rgb_flipped_horz);plt.title("Horizontal Flip");
plt.subplot(142);plt.imshow(img_NZ_rgb_flipped_vert);plt.title("Vertical Flip")
plt.subplot(143);plt.imshow(img_NZ_rgb_flipped_both);plt.title("Both Flipped")
plt.subplot(144);plt.imshow(img_NZ_rgb);plt.title("Original")

Вот и всё! Второй маленький шажок к человеку-фотошопу пройден! До встречи в следующих сериях.

Комментарии (5)


  1. Yuribtr
    25.07.2022 08:16
    +1

    Вопрос автору - а вы можете сформулировать конечную цель курса? А то в первой части я нашел, что это просто перевод туториала. А в картинке у вас написано что это ускоренный курс. По итогу, какие знания в хотите дать читателям?

    Если у вас будет несколько статей, то удобно было бы сделать оглавление и ссылки вперед / назад в каждой статье, например как сделано здесь.

    И опечатку в картинке затравки поправьте - там пропущена буква "o" в слове "Course", и звучит угрожающе.


    1. Halt
      25.07.2022 08:46

      Crash Curse — проклятие аварии. Самокритичненько.


      1. julicq
        25.07.2022 13:33

        Там даже Crush Curse - проклятье дробилки...


        1. Halt
          25.07.2022 21:35
          +1

          Скорее проклятье любимой.


    1. goshkalinin Автор
      25.07.2022 13:26

      ...ещё и в "crash" опечатался, так что это звучало максимально угрожающе!

      Это-таки перевод ускоренного курса, действительно. Он обещает провести читателя от открытия картинки до распознавания объектов.

      Прикручу оглавление со ссылками с третьей части. А то если два перевода на месяц (или годы) застрянут с некликабельными главками, это будет как-то не очень.