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

Блок 1. PIL как вход в работу с изображениями

Для чего нужен этот блок

Этот блок нужен, чтобы спокойно войти в тему обработки изображений и понять самую важную идею:

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

После изучения этого материала ты должен уметь:

  • открывать изображение;
  • узнавать его размер;
  • получать цвет отдельного пикселя;
  • менять пиксели;
  • проходить по изображению циклами;
  • вырезать фрагмент изображения;
  • менять размер картинки;
  • переводить изображение в оттенки серого;
  • сохранять результат в новый файл.

1. Что такое изображение

Любая картинка состоит из маленьких точек — пикселей.

У каждого пикселя есть цвет. Если изображение цветное, то обычно цвет задается тремя числами:

  • R — красный;
  • G — зеленый;
  • B — синий.

Например:

  • (255, 0, 0) — красный;
  • (0, 255, 0) — зеленый;
  • (0, 0, 255) — синий;
  • (255, 255, 255) — белый;
  • (0, 0, 0) — черный.

Обычно каждое из этих чисел лежит в диапазоне от 0 до 255.

Значит, изображение можно воспринимать как таблицу, где в каждой клетке хранится цвет.


2. Установка Pillow

Библиотека PIL в современном Python используется через пакет Pillow.

Установка:

pip install pillow

Подключение в программе:

from PIL import Image

3. Открытие изображения

Первое, что нужно уметь, — загрузить изображение из файла.

from PIL import Image

img = Image.open("picture.png")
print(img)

Что происходит

  • Image.open(...) открывает файл изображения;
  • img — это объект изображения;
  • дальше с ним можно работать: узнавать размер, менять пиксели, сохранять результат.

4. Размер изображения

У изображения есть:

  • ширина — количество пикселей по горизонтали;
  • высота — количество пикселей по вертикали.
from PIL import Image

img = Image.open("picture.png")
print(img.size)

Можно получить значения отдельно:

from PIL import Image

img = Image.open("picture.png")
width, height = img.size

print("Ширина:", width)
print("Высота:", height)

Важно

Порядок всегда такой:

(width, height)

То есть сначала ширина, потом высота.


5. Координаты пикселя

У каждого пикселя есть координаты:

  • x — столбец;
  • y — строка.

Левый верхний угол изображения имеет координаты:

(0, 0)

Если у изображения ширина width и высота height, то:

  • x принимает значения от 0 до width - 1;
  • y принимает значения от 0 до height - 1.

Пример

Если изображение имеет размер 5 x 4, то:

  • x может быть 0, 1, 2, 3, 4;
  • y может быть 0, 1, 2, 3.

6. Получение цвета пикселя

Можно узнать цвет конкретного пикселя по его координатам.

from PIL import Image

img = Image.open("picture.png")
pixel = img.getpixel((10, 20))
print(pixel)

Если изображение цветное, ответ может быть таким:

(123, 200, 45)

Это значит:

  • красный = 123;
  • зеленый = 200;
  • синий = 45.

7. Режим изображения

Изображения могут храниться в разных режимах.

Полезно знать несколько основных:

  • RGB — цветное изображение;
  • L — оттенки серого;
  • RGBA — цветное изображение с прозрачностью.
from PIL import Image

img = Image.open("picture.png")
print(img.mode)

Что это значит

  • RGB — у пикселя 3 числа;
  • L — у пикселя 1 число;
  • RGBA — у пикселя 4 числа.

На первом этапе лучше всего работать с изображениями в режиме RGB.


8. Изменение одного пикселя

Можно вручную поменять цвет одного пикселя.

from PIL import Image

img = Image.open("picture.png")
img.putpixel((10, 20), (255, 0, 0))
img.save("result.png")

Здесь пиксель с координатами (10, 20) стал красным.

Крайне важно

Результат лучше сохранять в новый файл, чтобы не испортить исходное изображение.


9. Почему лучше сохранять в новый файл

Полезная привычка:

  • исходное изображение не менять;
  • результат сохранять отдельно.

Например:

  • было picture.png;
  • стало result.png.

Так удобнее сравнивать исходник и результат, а также проще исправлять ошибки.


10. Изменение нескольких пикселей

Теперь можно менять не один пиксель, а сразу много.

Например, закрасим квадрат 50 x 50 в левом верхнем углу красным цветом.

from PIL import Image

img = Image.open("picture.png")

for x in range(50):
    for y in range(50):
        img.putpixel((x, y), (255, 0, 0))

img.save("red_corner.png")

Что делает этот код

  • перебирает все x от 0 до 49;
  • перебирает все y от 0 до 49;
  • каждый такой пиксель красит в красный цвет.

11. Проход по всему изображению

Во многих задачах нужно обработать все пиксели изображения.

Например:

  • сделать инверсию цветов;
  • затемнить картинку;
  • найти яркие области;
  • подсчитать число белых пикселей.
from PIL import Image

img = Image.open("picture.png")
width, height = img.size

for x in range(width):
    for y in range(height):
        pixel = img.getpixel((x, y))
        print(x, y, pixel)

Замечание

Печатать все пиксели большого изображения не стоит: их слишком много. Такой обход нужен в первую очередь для обработки.


12. Инверсия цветов

Очень хорошее первое упражнение.

Инверсия делается так:

  • новый красный = 255 - старый красный;
  • новый зеленый = 255 - старый зеленый;
  • новый синий = 255 - старый синий.

Примеры:

  • (0, 0, 0) превращается в (255, 255, 255);
  • (255, 0, 0) превращается в (0, 255, 255).
from PIL import Image

img = Image.open("picture.png")
width, height = img.size

for x in range(width):
    for y in range(height):
        r, g, b = img.getpixel((x, y))
        img.putpixel((x, y), (255 - r, 255 - g, 255 - b))

img.save("inverted.png")

Главная идея

Здесь очень хорошо видно, что обработка изображения — это обычная работа с числами.


13. Более удобный доступ к пикселям: load()

У PIL есть способ работать с пикселями удобнее.

from PIL import Image

img = Image.open("picture.png")
pixels = img.load()

print(pixels[10, 20])

Изменение пикселя:

from PIL import Image

img = Image.open("picture.png")
pixels = img.load()

pixels[10, 20] = (255, 0, 0)

img.save("result.png")

Почему это удобно

Теперь можно писать pixels[x, y], почти как доступ к элементу массива.


14. Инверсия через load()

Тот же алгоритм можно записать удобнее.

from PIL import Image

img = Image.open("picture.png")
pixels = img.load()
width, height = img.size

for x in range(width):
    for y in range(height):
        r, g, b = pixels[x, y]
        pixels[x, y] = (255 - r, 255 - g, 255 - b)

img.save("inverted.png")

Этот способ часто удобнее, чем постоянные вызовы getpixel() и putpixel().


15. Вырезание фрагмента изображения

Иногда нужна не вся картинка, а только ее часть.

Например:

  • область с объектом;
  • лицо;
  • фрагмент поля;
  • участок с текстом.

Для этого используется crop.

from PIL import Image

img = Image.open("picture.png")
fragment = img.crop((100, 50, 300, 200))
fragment.save("fragment.png")

Как работает crop

Параметры задаются так:

(left, top, right, bottom)

То есть:

  • левая граница = 100;
  • верхняя = 50;
  • правая = 300;
  • нижняя = 200.

Это не ширина и высота, а именно границы прямоугольника.


16. Как понимать crop

Удобно думать так:

  • (left, top) — координаты левого верхнего угла;
  • (right, bottom) — координаты правого нижнего угла.

Если хочешь вырезать область, где:

  • x идет от 100 до 299;
  • y идет от 50 до 199,

то можно написать:

img.crop((100, 50, 300, 200))

17. Изменение размера изображения

Иногда изображение нужно уменьшить или увеличить.

from PIL import Image

img = Image.open("picture.png")
small = img.resize((200, 150))
small.save("small.png")

Теперь у новой картинки:

  • ширина 200;
  • высота 150.

Очень важно

resize возвращает новое изображение. Исходная картинка при этом не изменяется.


18. Перевод в оттенки серого

Это хороший мост к следующим темам.

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

from PIL import Image

img = Image.open("picture.png")
gray = img.convert("L")

gray.save("gray.png")

Что означает L

Это режим grayscale, то есть оттенки серого.

Теперь пиксель выглядит не как (r, g, b), а как одно число, например:

137

Проверка:

from PIL import Image

img = Image.open("picture.png")
gray = img.convert("L")

print(gray.getpixel((10, 20)))

19. Создание изображения с нуля

Изображение можно не только открывать из файла, но и создавать самостоятельно.

from PIL import Image

img = Image.new("RGB", (300, 200), (255, 255, 255))
img.save("white.png")

Что здесь происходит

Создается новая картинка:

  • режим RGB;
  • размер 300 x 200;
  • цвет фона белый.

20. Рисуем фигуру пикселями

Теперь можно создать изображение и нарисовать на нем, например, синий квадрат.

from PIL import Image

img = Image.new("RGB", (200, 200), (255, 255, 255))
pixels = img.load()

for x in range(50, 150):
    for y in range(50, 150):
        pixels[x, y] = (0, 0, 255)

img.save("blue_square.png")

Что полезно понять

Это уже почти полноценная генерация изображения через код.


21. Частые ошибки новичка

1. Перепутаны ширина и высота

Правильно:

width, height = img.size

2. Перепутаны x и y

  • x — горизонталь;
  • y — вертикаль.

3. Выход за границы изображения

Если ширина width, то последний допустимый x — это:

width - 1

Если высота height, то последний допустимый y — это:

height - 1

4. Результат не сохранен

Если не вызвать save, изменения не попадут в файл.

5. Перезаписан исходник

Лучше сохранять в отдельный файл.

6. Неправильное понимание crop

crop((left, top, right, bottom)) — это границы, а не (x, y, width, height).


22. Минимум, который нужно запомнить

from PIL import Image

Открыть изображение:

img = Image.open("picture.png")

Размер:

width, height = img.size

Режим:

print(img.mode)

Получить пиксель:

pixel = img.getpixel((x, y))

Изменить пиксель:

img.putpixel((x, y), (255, 0, 0))

Удобный доступ к пикселям:

pixels = img.load()
print(pixels[x, y])
pixels[x, y] = (0, 255, 0)

Вырезать фрагмент:

part = img.crop((left, top, right, bottom))

Изменить размер:

small = img.resize((200, 200))

Перевести в серый:

gray = img.convert("L")

Сохранить:

img.save("result.png")

23. Готовые примеры

Пример 1. Открыть картинку и вывести размер

from PIL import Image

img = Image.open("picture.png")
width, height = img.size

print("Ширина:", width)
print("Высота:", height)
print("Режим:", img.mode)

Пример 2. Узнать цвет конкретного пикселя

from PIL import Image

img = Image.open("picture.png")

x = 20
y = 30

pixel = img.getpixel((x, y))
print("Пиксель:", pixel)

Пример 3. Сделать один пиксель красным

from PIL import Image

img = Image.open("picture.png")
img.putpixel((10, 10), (255, 0, 0))
img.save("one_red_pixel.png")

Пример 4. Закрасить угол изображения

from PIL import Image

img = Image.open("picture.png")
pixels = img.load()

for x in range(100):
    for y in range(100):
        pixels[x, y] = (255, 0, 0)

img.save("corner.png")

Пример 5. Инверсия цветов

from PIL import Image

img = Image.open("picture.png")
pixels = img.load()
width, height = img.size

for x in range(width):
    for y in range(height):
        r, g, b = pixels[x, y]
        pixels[x, y] = (255 - r, 255 - g, 255 - b)

img.save("invert.png")

Пример 6. Вырезать центральную часть изображения

from PIL import Image

img = Image.open("picture.png")
width, height = img.size

left = width // 4
top = height // 4
right = 3 * width // 4
bottom = 3 * height // 4

fragment = img.crop((left, top, right, bottom))
fragment.save("center.png")

Пример 7. Уменьшить изображение

from PIL import Image

img = Image.open("picture.png")
small = img.resize((200, 200))
small.save("small.png")

Пример 8. Перевести в серый

from PIL import Image

img = Image.open("picture.png")
gray = img.convert("L")
gray.save("gray.png")

24. Практические задания

Задание 1

Открой изображение и выведи:

  • ширину;
  • высоту;
  • режим изображения.

Задание 2

Сделай красным квадрат 50 x 50 в левом верхнем углу.

Задание 3

Сделай инверсию всей картинки.

Задание 4

Вырежи центральную четверть изображения.

Задание 5

Создай новую белую картинку и нарисуй на ней синий прямоугольник.


25. Почему это важно для олимпиадных задач

Даже этот базовый блок уже полезен для олимпиадной обработки изображений.

Если в задаче нужно:

  • найти объект на картинке;
  • проанализировать область;
  • выделить часть изображения;
  • посчитать признаки по пикселям,

то сначала нужно научиться уверенно работать с картинкой как с набором данных.

Именно это и дает PIL на первом этапе.


26. Итог блока

После этого блока изображение должно восприниматься не как «фото», а как таблица пикселей, которой можно управлять программой.

Это и есть фундамент для дальнейшей работы с cv2, масками, бинаризацией и более серьезной обработкой.


27. Базовый шаблон

from PIL import Image

img = Image.open("picture.png")
width, height = img.size
print(img.mode)

pixel = img.getpixel((x, y))
img.putpixel((x, y), (255, 0, 0))

pixels = img.load()
print(pixels[x, y])
pixels[x, y] = (0, 255, 0)

part = img.crop((left, top, right, bottom))
small = img.resize((200, 200))
gray = img.convert("L")

img.save("result.png")