Привет, Хабр! На связи команда регионального научно-образовательного центра «Искусственный интеллект и анализ больших данных» при НГТУ им. Р. Е. Алексеева. При поддержке компании YADRO мы изучаем архитектуру RISC-V и компьютерное зрение, чтобы внедрить результаты в учебный процесс.

Предлагаем вместе с нами проверить, на что способен одноплатный компьютер Lichee Pi 4A в задачах обработки изображений, несмотря на его ограниченные ресурсы. А заодно — получить базовые навыки по разработке систем компьютерного зрения. Пройдем путь от настройки системы до отслеживания кликов по картинке и распознавания объектов с моделью YOLOX.

Кратко расскажем, что ждет вас в статье. Мы создадим виртуальное окружение Python и установим подходящую среду разработки — Visual Studio Code. Далее перейдем к работе с изображениями. Начнем с базовых операций, таких как получение информации о файле, изменение отдельных пикселей и работа с цветовыми каналами. 

Затем освоим более продвинутые приемы: динамическую перекраску, фильтрацию, обрезку, вращение и комбинированные преобразования. Также коснемся интерактивной работы с изображениями — научимся отслеживать клики пользователя и добавлять графические элементы. И в завершении распознаем объекты на картинке. 

Почему мы выбрали библиотеку Pillow и Lichee Pi 4A

Pillow — это мощная и удобная библиотека для работы с изображениями в Python. Она позволяет выполнять основные операции: изменять размер, фильтровать и поворачивать изображения, а также конвертировать в другие форматы. Мы решили узнать, насколько эффективно Lichee Pi 4A справится с этими задачами и можно ли его использовать для базовых операций компьютерного зрения.

Прикрепленное изображение
Lichee Pi 4A

Lichee Pi 4A — это одноплатный компьютер (single-board computer, SBC) на базе четырехъядерного процессора Alibaba T-Head TH1520 RISC-V Xuantie C910 2,0 ГГц и графического процессора Imagination. За ускорение ИИ-задач отвечает нейронный процессор (NPU), производительность которого составляет 4 TOPS. В целом Lichee Pi 4A по вычислительной мощности сравним с Raspberry Pi 4. 

У Lichee Pi 4A есть широкий набор интерфейсов для подключения оборудования: USB 3.0, USB Type-C, HDMI 2.0, два интерфейса для камер (1×4-lane MIPI CSI и 1×2-lane MIPI CSI), гигабитный Ethernet, Wi-Fi и Bluetooth.

Основные преимущества Lichee Pi 4A:

  • Компактный размер и низкое энергопотребление позволяют использовать устройство для встраиваемых систем, мобильных решений и проектов в области интернета вещей.

  • Цена 12–14 тысяч рублей (на момент покупки) делает микрокомпьютер привлекательным вариантом для образовательных целей, проведения экспериментов и быстрого прототипирования.

  • Широкий набор интерфейсов обеспечивает гибкость при подключении периферийных устройств и внешних сенсоров.

  • Поддержка ОС Linux и Android значительно упрощает разработку, тестирование и портирование ПО.

Мы выбрали именно Lichee Pi 4A, а не Raspberry Pi 4 по трем причинам:

  • Платформа построена на перспективной архитектуре RISC-V, которая быстро набирает популярность в мире открытых вычислений.

  • Наличие NPU делает Lichee Pi 4A особенно интересным для экспериментов с компьютерным зрением.

  • Работа с менее распространенным оборудованием развивает навыки адаптации и оптимизации ПО под новые аппаратные решения — важную компетенцию для инженеров, которые специализируются на интернете вещей и локальном ИИ на периферийных устройствах (Edge AI).

Теперь перейдем к практическим шагам и займемся настройкой нужного ПО. 

Устанавливаем и настраиваем инструменты 

Для начала нам нужно подготовить базовую конфигурацию среды для работы с Python — нам поможет документация настройки окружения для Lichee Pi 4A. 

Обновим список пакетов и установим актуальные версии библиотек и утилит:

sudo apt update
sudo apt upgrade -y

Ключ -y автоматически подтверждает установку всех обновлений без дополнительных вопросов от пакетного менеджера. Мы будем использовать его, чтобы ускорить процесс настройки.

Устанавливаем основные инструменты:

sudo apt install wget git vim -y

  • wget — загрузка файлов через терминал,

  • git — система управления версиями для клонирования и работы с репозиториями,

  • vim — удобный текстовый редактор в командной строке

Проверяем версию Python:

python3 --version

Обновить или установить Python на Linux поможет официальная документация.

Устанавливаем библиотеку SHL

Она нужна для работы с Lichee Pi 4A, порядок установки также описан в документации к устройству. Архив с библиотекой после скачивания нужно распаковать, а библиотечные файлы скопировать в системные директории. 

Настраиваем менеджер пакетов и создаем виртуальное окружения

Чтобы упростить управление зависимостями Python-проектов, мы установили менеджер пакетов pip и модуль venv для создания виртуальных сред:

sudo apt install python3-pip python3-venv -y

После этого создаем и активируем виртуальную среду:

cd ~
python3 -m venv ort
source ~/ort/bin/activate

Так мы изолируем зависимости каждого проекта и обеспечиваем стабильность работы приложений на Lichee Pi 4A.

Теперь у нас есть полностью подготовленное рабочее окружение. Можно переходить к установке дополнительных библиотек Python и разработке наших проектов. Настроенное виртуальное окружение позволяет управлять установленными пакетами и минимизировать возможные конфликты между версиями библиотек.

Visual Studio Code для разработки

Для удобства разработки мы решили установить интегрированную среду разработки (IDE) Visual Studio Code, которая поддерживает работу с Python и другими языками программирования через расширения. Мы выбрали Visual Studio Code, потому что это кроссплатформенный инструмент с удобной интеграцией с Git, встроенным терминалом, отладчиком и поддержкой удаленной разработки. Кроме того, его активно развивает и поддерживает сообщество разработчиков.

Lichee Pi 4A основан на архитектуре RISC-V, поэтому официальная сборка Visual Studio Code для него недоступна. Мы установили VS Code на x86_64-машину разработчика и подключались к микрокомпьютеру по SSH. Код писали и редактировали локально, а запускали и отлаживали удаленно на Lichee Pi 4A.

Мы также протестировали VSCodium — open source-альтернативу VS Code. У этого редактора есть экспериментальные сборки для архитектуры RISC-V, которые можно собрать вручную, например с помощью QEMU или в chroot-среде. Нам удалось запустить VSCodium на Lichee Pi 4A, но его производительность оказалась низкой для полноценной работы, поэтому поэтому рекомендуем писать код удаленно, с основной х86_64-машины.

Операционная система Lichee Pi 4A создана на базе Debian/Ubuntu, поэтому на локальную машину мы установили Debian через .deb-пакет. Его можно скачать с официального сайта

Сначала скачиваем .deb-пакет для 64-разрядной версии:

wget https://update.code.visualstudio.com/latest/linux-deb-x64/stable -O vscode.deb

Устанавливаем пакет через apt:

sudo apt install ./vscode.deb

Если у вас старая версия дистрибутива, возможно, сначала придется установить пакет через dpkg, а затем исправить зависимости:

sudo dpkg -i vscode.deb
sudo apt-get install -f

Установка .deb-пакета автоматически добавляет репозиторий Visual Studio Code в систему и настраивает ключ подписи. Так можно получать обновления через стандартный механизм обновления пакетов apt:

sudo apt install apt-transport-https
sudo apt update
sudo apt install code

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

Базовые приемы обработки изображений с Pillow

Начнем с основ — анализа картинки и базовых манипуляций, таких как изменение цвета пикселя и смена цветовой модели.

Получаем основную информацию об изображении

Первый шаг — загрузить изображение, определить его цветовую модель и получить информацию о его размере и границах.

from PIL import Image
from PIL import ImageFilter
img = Image.open(“flower.jpg”)
print(img.size, img.format, img.mode)
C:\Users\veron\Downloads\Telegram Desktop\Screenshot_2024-07-20_18-00-28 (2).png

Эти базовые данные нам нужны для дальнейшей работы с изображением, анализ и визуализацию. 

Меняем цвет пикселя

К отдельным пикселям можно обращаться с помощью метода load() из библиотеки Pillow. Так мы сможем изменять цветовые значения точечно, а это основа для различных операций по обработке изображений.

Открываем white.jpg с помощью Pillow:

from PIL import Image
img = Image.open("white.jpg")
obj = img.load()

Выбираем пиксель с координатами (25, 45) и меняем его цвет:

obj[25, 45] = (0, 0, 0)  # Новый цвет: черный (RGB: 0, 0, 0)

Сохраняем отредактированное изображение:

img.save("image3.jpg")

Визуально проверяем, что цвет пикселя изменился. 

Использование метода load() позволяет напрямую работать с массивом пикселей изображения: читать, модифицировать и анализировать отдельные элементы, не копируя данные в отдельные структуры. Это особенно важно для задач, которые требуют высокую производительности при обработке больших изображений.

C:\Users\veron\Downloads\Telegram Desktop\Screenshot_2024-07-20_18-21-37 (2).png

Меняем цветовую модель

Чтобы лучше понять структуру цифровых изображений, научимся работать с цветовыми каналами с помощью методов split() и merge() из библиотеки Pillow.

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

from PIL import Image
img = Image.open("flower.jpg")
print(img.mode)  # Проверили текущую цветовую модель: "RGB"

Разделяем изображение на три независимых канала: красный (R), зеленый (G) и синий (B):

R, G, B = img.split()

Создаем новый альфа-канал (mask), который заполнен полупрозрачными пикселями:

mask = Image.new("L", img.size, 128)  # 128 из 255 — полупрозрачность

Здесь L — это режим изображения, который обозначает одноканальное изображение в градациях серого (8 бит на пиксель). Он часто используется для масок и альфа-каналов, где:

  • 0 соответствует полной прозрачности,

  • 255 — полной непрозрачности,

  • значения между ними — различным степеням полупрозрачности.

Подробнее о режимах в Pillow читайте в официальной документации.

Объединяем каналы в изображение RGBA-формата:

img2 = Image.merge("RGBA", (R, G, B, mask))
print(img2.mode)  # Теперь "RGBA"
img2.show()

Зачем нам нужно уметь менять цветовую модель:

  • split() позволяет независимо работать с отдельными цветовыми компонентами изображения.

  • merge() дает возможность собрать новое изображение, внося изменения в отдельные каналы или добавляя новые (например, управление прозрачностью через альфа-канал).

  • Переход к модели RGBA открывает доступ к техникам наложения, маскирования, работы с прозрачными слоями — это нужно для создания сложной графики или анимации.

Новое изображение и более сложные манипуляции

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

Нарисуем черный квадрат 100×100 пикселей:

from PIL import Image
img = Image.new("RGB", (100, 100))  # Черный квадрат
img.save("qwerty.jpg")

Он будет нашим первым тестовым объектом для экспериментов.

Также нарисуем прямоугольник размером 320×240 пикселей и заполним его пурпурным цветом (RGB: 205, 100, 200):

from PIL import Image
img = Image.new("RGB", (320, 240), "rgb(205,100,200)")
img.save("qwerty2.jpg")

Эксперименты с различными значениями каналов RGB помогли нам добиться яркого и насыщенного оттенка. Этот навык пригодится для проектирования цветовых схем и тем оформления.

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

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

from PIL import Image
img = Image.new("RGB", (320, 240))
for x in range(320):
    for y in range(240):
        r = int(x / 3)        # Зависимость красного от горизонтальной координаты
        g = int((x + y) / 6)  # Зависимость зеленого от суммы координат
        b = int(y / 3)        # Зависимость синего от вертикальной координаты
        img.putpixel((x, y), (r, g, b))
img.save("okno2.png")

Мы намеренно используем метод putpixel() вместо более производительного load(), который обеспечивает быстрый доступ к пикселям и лучше подходит для задач массовой обработки изображений. putpixel() позволяет наглядно показать принцип адресной записи цвета по координатам и не вводить дополнительных переменных, поэтому лучше подходит для обучения. 

Наша формула учитывает горизонтальное и вертикальное положение каждого пикселя, мы смогли получить плавные переходы и динамичные визуальные эффекты.

Эксперименты с различными математическими выражениями позволили нам добиться красивых, плавно переходящих оттенков, которые создают эффект мерцания и глубины:

Меняем цветовые каналы 

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

from PIL import Image
img = Image.open("flower.jpg")
for x in range(img.size[0]):
    for y in range(img.size[1]):
        r, g, b = img.getpixel((x, y))
        img.putpixel((x, y), (b, r, g))
img.save(“okne2.jpg”)

Мы перебрали все пиксели исходного изображения и извлекли для каждого значения трех цветовых каналов: R (красный), G (зеленый) и B (синий). Эти числовые значения хранят информацию об интенсивности соответствующего цвета в данном пикселе.

Продолжим экспериментировать и сделаем перестановку каналов RGB: R станет B, G — R, а B — G. Так мы получим новые значения для каждого пикселя, которые отражают смещение цветовых компонент.

Наконец, попробуем перемешать каналы: 

from PIL import Image
img = Image.open("picture.jpg")
for x in range(img.size[0]):
    for y in range(img.size[1]):
        r, g, b = img.getpixel((x, y))
        img.putpixel((x, y), (b, r, g))
img.save(“okne.jpg”)

Мы визуализировали картинку с новыми значениями R, G и B для каждого пикселя. В результате получился довольно необычный эффект:

Учимся работать с отдельными цветовыми каналами

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

Методом split() выделим три отдельных канала RGB: красный, зеленый и синий в виде кортежа. Каждый канал — это отдельное черно-белое изображение, которое хранит информацию об интенсивности своего цвета в каждом пикселе.

Теперь соберем изображение обратно из трех каналов с помощью конструкции Image.merge(). Для этого передадим ей режим RGB и три выделенных ранее канала, чтобы объединить черно-белые изображения в одно цветное: 

from PIL import Image
img = Image.open("picture.jpg")
r,g,b = img.split()
img2 = Image.merge(“RGB”, (r,g,b))
img.save(“o.jpg”)

Мы восстановили исходное изображение:

Строим гистограммы

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

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

Теперь нам нужно перебрать все пиксели изображения и извлечь значения каналов R, G и B для каждого пикселя. Добавляем полученное количество пикселей и их значения в список гистограммы. 

import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import matplotlib
matplotlib.use(‘Tkagg’)
im = Image.open(“picture.jpg”)
plt.figure(figsize = (8,6))
plt.hist(nр.array(im).flatten(), bins = 256, color = ‘gray’)
plt.title(‘Histogram of the Image’)
plt.xlabel(‘Pixel Value’)
plt.ylabel(‘Frequency’)
plt.show()

Наконец, с помощью значений из списка строим три графика гистограммы, которые показывают распределение интенсивности для красного, синего и зеленого каналов: 

Копируем и меняем размер

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

Затем снова открываем изображение и получаем его исходные размеры. После этого изменяем размер изображения пропорционально с помощью метода thumbnail(). Он изменяет изображение и не возвращает новую копию, поэтому в результате оригинал заменяется уменьшенной версией. Если нужно, то полученное изображение можно сохранить отдельно.

Далее мы снова открываем исходное изображение, определяем его размеры и создаем версию с новыми параметрами методом resize(). В отличие от thumbnail() этот метод не затрагивает исходный файл, а возвращает отдельное изображение с заданными размерами. Его также можно сохранить отдельно.

Таким образом, thumbnail() подходит для быстрого уменьшения размера, когда изменения оригинала допустимы, а resize позволяет получить изображение нужного размера без потери исходного файла.

from PIL import Image
img = Image.open("picture.jpg")
img2 = img.copy()
print("Размер копии:", img2.size)
img2.thumbnail((400, 300))
print("Новый размер после thumbnail:", img2.size)
imgrn = img.resize((300, 300))  # Создаем новое изображение размером 300x300
imgrn.save("be_like6.jpg")

Вырезаем фрагмент и меняем его размер

Открываем исходное изображение и с помощью метода crop() выделяем прямоугольную область, задав ее координаты в виде кортежа box = (300, 300, 500, 500). Значения указывают левую верхнюю и правую нижнюю точки прямоугольника, который нам нужен.

Мы получили новое изображение, которое содержит только выделенный фрагмент. Теперь мы можем с ним работать, не меняя остальную часть картинки. 

Методом resize() изменим размеры фрагмента до 200x200 пикселей и получаем новое изображение. В отличие от crop() он сохраняет оригинал без изменений.

from PIL import Image
img = Image.open("picture.jpg")
box = (300, 300, 500, 500)  # (левая, верхняя, правая, нижняя границы)
img_crop = img.crop(box)
new_size = (200, 200)
img_resized = img_crop.resize(new_size)
img_resized.save("bee.jpg")

В результате мы получили уменьшенную версию выбранной области, при этом исходное изображение осталось без изменений: 

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

Вращение и отражение изображения

Изменение ориентации изображения — полезная операция в обработке графических данных, особенно для задач дополнения данных (data augmentation) в машинном обучении и при создании эффектов в графических приложениях. Разберемся с основными способами вращения изображения с помощью библиотеки Pillow.

Открываем исходное изображение и получаем его размеры:

from PIL import Image
img = Image.open("picture.jpg")
print("Исходный размер изображения:", img.size)

Поворачиваем изображение на 90° без изменения размеров холста:

img_rotated_90 = img.rotate(90)
print("Размер после поворота на 90°:", img_rotated_90.size)

Поворачиваем изображение на 45°:

img_rotated_45 = img.rotate(45)
img_rotated_45.save("rotated_45.jpg")

Часть изображения обрезается, поэтому воспользуемся параметром (expand=True), чтобы автоматически увеличить размер холста:

img_rotated_45_expand = img.rotate(45, expand=True)
img_rotated_45_expand.save("rotated_45_expand.jpg")

Поворачиваем изображение на 180° с параметром (expand=True):

img_rotated_180_expand = img.rotate(180, expand=True)
img_rotated_180_expand.save("rotated_180_expand.jpg")

Запомним, что без expand=True библиотека Pillow старается сохранить исходные размеры, поэтому изображение может быть обрезано. Если использовать этот параметр, то библиотека автоматически увеличит размеры холста, чтобы вместить повернутую картинку без обрезки. 

А теперь перейдем к зеркальным отражениям. Они могут пригодится для подготовки дополнительных вариантов изображений в системах компьютерного зрения. Сделаем зеркальные отражения картинки по горизонтали и вертикали с помощью метода transpose()

img_flipped_horizontal = img.transpose(Image.FLIP_LEFT_RIGHT)
img_flipped_vertical = img.transpose(Image.FLIP_TOP_BOTTOM)
img_flipped_horizontal.save("flipped_horizontal.jpg")
img_flipped_vertical.save("flipped_vertical.jpg")

Закрашиваем изображения нужным цветом

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

Закрасим прямоугольную область в верхнем левом углу с помощью метода paste(). Для этого передадим ему кортеж RGB для красного цвета, которым хотим залить область, и координаты этой области: 

from PIL import Image
img = Image.open("picture.jpg")
img.paste((255, 0, 0), (0, 0, 500, 500))  # Координаты: (левая, верхняя, правая, нижняя границы)
img.save("6lab1.jpg")

Теперь снова открываем наше изображение и полностью заливаем его зеленым цветом с помощью paste(). Для чего передаем кортеж RGB для зеленого цвета и координаты всего изображения, которые мы получили методом getbbox():

img.paste((0, 128, 0), img.getbbox())  # getbbox() возвращает весь прямоугольник изображения
img.save("6lab2.jpg")

Проводим комбинированные манипуляции

Такие операции позволяют накладывать графические элементы, чтобы создавать новые изображения. 

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

from PIL import Image
img = Image.open("picture.jpg")
img_small = img.resize((200, 150))
img.paste((255, 0, 0), (9, 9, 211, 161))  # Прямоугольник под рамку
img.paste(img_small, (10, 10))

Теперь добавим новый графический элемент. Нарисуем белую полупрозрачную горизонтальную полосу и наложим ее на изображение: 

white_bar = Image.new("RGB", (img.size[0], 100), (255, 255, 255))  # Белая полоса
mask = Image.new("L", (img.size[0], 100), 160)  # Маска с прозрачностью
img.paste(white_bar, (0, 0), mask)

Наконец, вставим уменьшенное изображение в центр исходного:

box = (200, 200, 400, 400)
img_crop = img.crop(box)
img_resized = img_crop.resize((300, 300))
img_rotated = img_resized.rotate(90)
img.paste(img_rotated, (230, 80))
img.save("picturik.jpg")

Выводим координаты клика по точке на картинке

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

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
img = np.array(Image.open("picture.jpg"))
plt.imshow(img)
plt.axis('off')
print("Кликните на 3 точки на изображении")
points = plt.ginput(3)  # Ожидаем 3 клика мышью
print("Координаты кликов:", points)
plt.show()

Пользователь сделал три клика, и программа вывела координаты этих точек в консоль: 

Координаты кликов: [(123.5, 245.2), (300.7, 150.4), (450.3, 75.8)]

Рисуем графические элементы

Для начала нам нужно импортировать две библиотеки:

  • from PIL import Image — чтобы работать с изображениями,

  • import matplotlib.pyplot as plt — чтобы использовать функций Matplotlib

Выведем изображение на экран с помощью функции imshow(img) из Matplotlib и временно отключим отображение координатных осей с помощью axis('off'). Важно понимать, что при этом рисование происходит не на самом изображении, а на холсте визуализации Matplotlib. То есть оригинальный файл не изменяется — мы работаем с его копией в виде массива.

Теперь определим координаты точек, которые хотим визуализировать, — для этого создадим списки x и y, которые содержат значения координат.

Нанесем две серии точек с помощью функции plot() из Matplotlib. Первая серия точек — это зеленые кружочки, которые соединены линией (plot(x,y,'go-')), а вторая — точки без линии, которые построены по первым двум элементам списков x и y (plot(x[:2], y[:2])).

Наконец, добавим заголовок plotting: 'picture.jpg' с помощью функции title(), чтобы подписать получившуюся визуализацию.

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
img = np.array(Image.open("picture.jpg"))
plt.imshow(img)
plt.axis('off')
x = [100, 100, 400, 400]
y = [200, 500, 200, 500]
plt.plot(x, y, 'go-')  # 'g' — зеленый цвет, 'o' — кружки, '-' — линии
plt.plot(x[:2], y[:2], 'bo-')  # Дополнительно синим цветом
plt.title('Plotting: "picture.jpg"')
plt.show()

Обратите внимание: если необходимо не просто визуализировать, а сохранить изображение с нанесенными элементами, лучше использовать библиотеку Pillow и модуль ImageDraw, которые позволяют вносить изменения непосредственно в изображение.

Обрабатываем картинки с помощью фильтров

С помощью фильтрации можно выделять детали или создавать художественные эффекты на изображениях. Разберемся, как работают три встроенных фильтра из модуля ImageFilter библиотеки Pillow.

Импортируем нужные модули:

from PIL import Image, ImageFilter

Загружаем исходное изображение:

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

Теперь применим три разных фильтра и посмотрим, как они работают и для чего нужны.

EMBOSS — фильтр, создающий эффект тиснения (embossing), придает изображению вид трехмерной текстуры и ощущение рельефности. Подходит для художественной обработки и стилизации изображений:

img_emboss = img.filter(ImageFilter.EMBOSS)
img_emboss.save("first_filter.jpg")

FIND_EDGES — фильтр, который выделяет границы объектов (edge detection). Это полезно для анализа форм, сегментации, построения контурных моделей, создания технических схем и предобработки перед детектированием:

img_edges = img.filter(ImageFilter.FIND_EDGES)
img_edges.save("second_filter.jpg")

CONTOUR — подчеркивает основные границы объектов, создавая четкий и графический вид:

img_contour = img.filter(ImageFilter.CONTOUR)
img_contour.save("third_filter.jpg")

Строим контуры

Этот инструмент выделяет структурные особенности изображения. Функция Contour(), как и одноименной фильтр, пригодится, если нужно создать техническую схему, иллюстрацию или визуализировать структуру изображения. 

Загружаем исходную картинку и преобразуем ее в оттенки серого — с черно-белым изображением работать будет проще. 

Исходное изображение
Исходное изображение

Вызываем функцию plt.contour() из Matplotlib, которая визуализирует линии равной интенсивности на изображении, чтобы выделить контуры объектов. Также отключим отображение осей координат с помощью plt.axis('off'), чтобы сосредоточиться на самой картинке.

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
img = np.array(Image.open("picture.jpg").convert('L'))
plt.figure()
plt.gray()  # Установить палитру серых оттенков
plt.contour(img, origin='image')
plt.axis('equal')
plt.axis('off')
plt.show()

Мы получили изображение с четко выделенными границами и контурами:

Мы рассмотрели базовые функции для работы с изображениями в библиотеки Pillow и познакомились с фильтрами Matplotlib. Теперь разберемся, как обнаруживать объекты на картинках.

Распознаем объекты с помощью модели YOLOX

YOLOX — это легковесная и быстрая модель обнаружения объектов, которая обеспечивает хороший баланс между точностью и скоростью. Она хорошо работает на одноплатных компьютерах, таких как Lichee Pi 4A, за счет оптимизированной архитектуры.

Для начала работы с YOLOX клонируем ее официальный репозиторий для архитектуры RISC-V, который содержит примеры. На вики Lichee Pi 4A есть подробная инструкция для этой модели. 

Теперь настроим среду. Экосистема ПО для RISC-V активно развивается, поэтому загрузим и установим предварительно скомпилированные версии необходимых Python-библиотек. Обратите внимание, что нужно установить специальную версию ONNX Runtime, которая оптимизирована для процессоров Gentej. Документация к Lichee Pi 4A рекомендует модифицированную версию HHB-onnxruntime, которая оптимизирована для платформы SHL.

Подготовим скрипт для запуска: в demo/ONNXRuntime нужно адаптировать пути к библиотекам и зависимостям для корректной работы на одноплатном компьютере. 

Для запуска инференса используем скрипт onnx_inference.py и указываем:

  • путь к предобученной модели YOLOX-S в формате ONNX,

  • путь к изображению для обработки,

  • выходной каталог для сохранения результатов,

  • параметры обнаружения: порог вероятности и размер входного изображения.

Работа модели YOLOX на Lichee Pi 4A
Работа модели YOLOX на Lichee Pi 4A

В результате работы скрипта мы получили новое изображение, на котором:

  • обнаруженные объекты выделены прямоугольниками,

  • у каждого объекта есть метка класса и степень уверенности модели (confidence score),

  • модель корректно распознала на картинке два человека и мяч.

Заключение

Одноплатный компьютер Lichee Pi 4A на архитектуре RISC-V доказал, что его вычислительных мощностей достаточно для обучения основами компьютерного зрения — от базовой обработки изображений с Python-библиотекой Pillow до распознавания объектов с моделью YOLOX. 

Уверены, что наш опыт будет полезен начинающим инженерам, которые хотят изучить основы компьютерного зрения и обработки изображений. Работа с Lichee Pi 4A поможет получить базовые навыки адаптации и оптимизации ПО под новые аппаратные решения, а это особенно важно, если вы планируете работать в сферах интернета вещей, встроенных систем или Edge AI. 

Если остались вопросы или хотите поделиться своим опытом — пишите в комментариях, будем рады обсудить.

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


  1. PuknulProgrammistamPodNos
    27.05.2025 13:11

    Добрый день! Большое спасибо за вашу статью, было не очень интересно читать, но во многом из-за того, что я сам хорошо знаком с компьютерным зрением. И обучал машины "видеть" я ещё тогда, когда многие программисты не умели правильно нажимать на кнопки клавиатуры. Из-за этого, конечно же, в последствии я был вынужден уйти в преподавание и вести курсы по программированию (преподавал как раз-таки основы нажимания на клавиши). Сейчас работаю макакой в вэбе, и в целом всё устраивает: труд здесь обезьяний, но зарплаты - конские. Впрочем, как и на любой другой айти-вакансии (и вас, надеюсь, работодатель не обделил зарплатой в 1000000кк/нс). Но душой и сердцем я всё равно лелею научные айти-направления, в том числе и нейронные сети. До сих пор, бывает, достаю свой одноплатный компьютер BeagleBone Black и запускаю на нём самолично обученную нейронку. Моя гордость, она создана чтобы имитировать работу обычного среднестатистического программиста. Сперва я не совсем это понял, ведь когда запустил её, компьютер ушёл в мёртвое зависание. Я долго кусал локти и размышлял, что же пошло не так. Не хватило мощей? или где-то ошибка в коде? а может и вовсе какая-то утечка памяти. Но потом я наконец осознал - никаких ошибок нет. Всё работает верно. Ведь мы, айтишники, на самом деле только и занимаемся на работе тем, что ничем не занимаемся. Лежим в своих уютных креслах, плюём в потолок. Мозги не думают, разумом мы витаем где-то в облаках. Собственно, поковыряв свой одноплатный компьютер с запущенной на нём нейронкой, я как раз-таки и обнаружил, что всё это время компьютер находится в режиме простоя, хотя сам SoC при этом грелся неимоверно - настолько прекрасно нейросеть имитировала рабочую айти-деятельность. Но, думаю, что вы, будучи айтишником, и сами прекрасно понимаете о чём я. Не вижу в программистском безделье и балованьи библиотекой pillow ничего плохого (особенно если этого до сих пор не уловило ни моё, ни ваше руководство), однако хотелось бы почитать про какие-то более узко-направленные вещи. Скажем, дообучали ли вы как-то модель или это она прямо сама сходу смогла распознать людей и мяч; как выглядит код с "скармливанием" нейронной сети картинки; выдаёт ли нейронка сама на выход вот эту картинку с оранжевыми рамочками и опознанными объектами или это делали вы сторонними методами... и прочее. Или вы и впрямь только запустили демопроект и всё? В общем, хотелось бы почитать настоящий инженерный опыт работы с узкоспециализированной штукой, а не лицезреть в очередной раз обычный айти-программистский опыт. Ну и ещё, вы выбрали для этих целей Lichee Pi 4A просто потому что он у вас уже когда-то был куплен и лежал вот до поры до времени, пылился? Всё-таки сейчас на рынке присутствует огромное количество одноплатных компьютеров нового поколения, которые как раз-таки и предназначены для работы с нейронными сетями. Погуглите, особенно присмотритесь к компьютеру под названием iPhuck. Я думаю, что для решения многих ваших задач его технических характеристик хватит впроголодь.


  1. rPman
    27.05.2025 13:11

    Какие интересные предложения у sipeed, и мини кластер на этих платах, и ноутбук и fpga платы с девбордой (платы в sodim ddr3 слот)... или к примеру микрокомпьютер (на три сантиметра)


    1. ponikrf
      27.05.2025 13:11

      Сейчас в плане железа все ушло очень далеко. Risc-v набирает популярность. И даже появились интересные варианты по доступным ценам (привет эльбрус/байкал) например Milk-V Jupiter. Улучшается поддержка периферии. И если 3 года назад про risc-v говорили как о чем то космическом, то сегодня это уже работает и даже есть из чего выбрать. На ютубе explainingcomputers обычно разбирает самые актуальные в доступном сегменте подобные железки.


  1. FenixFly
    27.05.2025 13:11

    Раз уж это дело показывается на RISC-V процессоре, то было бы полезно в статье раскрыть больше информации про то, как и кем собрана pillow под RISC-V (и откуда она устанавливается), какая у нее производительность например в сравнении с Raspberry. Сборка Pillow с какими-нибудь оптимизациями под RISC-V и ее бенчмаркинг тянет на отдельную статью.