Блок 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")