Анонимизация изображений с помощью Go
Перевод статьи “Anonymising images with Go and Machine Box”
В стандартной библиотеке Go есть достаточное количество мощных инструментов для работы с изображениями. Это пакеты image
, image/*
и draw
. В этом руководстве мы будем использовать эти инструменты совместно с Machine Box Go SDK для цензурирования изображений.
В рамках этой статьи мы напишем простую консольную утилиту. Но тот же самый код можно будет использовать, например, для написания веб-приложений.
Запускаем Facebox
Facebox это один из образов Machine Box который позволяет распознавать лица с помощью алгоритмов машинного обучения. Все это поставляется в виде Docker контейнера, который можно запускать где угодно, в том числе и локально.
В терминале можно запустить Facebox выполнив следующую команду:
docker run -p 8080:8080 -e "MB_KEY=$MB_KEY" machinebox/facebox
Обратите внимание - вам обязательно нужно указать переменную окружения MB_KEY
. Значение для этой переменной можно найти на странице вашего аккаунта https://machinebox.io/account. Естественно, вам нужно зарегистрироваться на machinebox.io.
Как только контейнер запустится, можно перейти по ссылке http://localhost:8080/
для доступа к консоли встроенной в Facebox.
Facebox можно обучить распознавать какие-то конкретные лица и затем использовать для поиска похожих лиц. Но в нашем примере распознавания лиц не будет. Нам достаточно получать информацию о том, где на фотографии расположено лицо.
Скачиваем Machine Box Go SDK
Устанавливаем SDK:
go get -u github.com/machinebox/sdk-go
Используем анонимные функции
Начнем с написания функции, которая принимает на вход исходное изображение и набор объектов facebox.Face
. Затем генерирует новый JPEG c зацензуренными лицами.
Создадим папку anon
и файл main.go
. Теперь можно приступать к написанию функции для анонимизации:
func anonymise(src image.Image, faces []facebox.Face) image.Image {
dstImage := image.NewRGBA(src.Bounds())
draw.Draw(dstImage, src.Bounds(), src, image.ZP, draw.Src)
for _, face := range faces {
faceRect := image.Rect(
face.Rect.Left,
face.Rect.Top,
face.Rect.Left+face.Rect.Width,
face.Rect.Top+face.Rect.Height,
)
facePos := image.Pt(face.Rect.Left, face.Rect.Top)
draw.Draw(
dstImage,
faceRect,
&image.Uniform{color.Black},
facePos,
draw.Src)
}
return dstImage
}
Мы используем image.NewRGBA
для создания нового изображения такого же размера, как исходное. Затем с помощью draw.Draw
копируем исходное изображение в только что созданное.
Итерируясь по всем объектам Face
в слайсе faces
мы сохраняем координаты прямоугольников в которых находятся лица в переменной faceRect
. Также сохраняем все позиции лиц на изображении в переменной facePos
.
Структуры facebox.Face
и facebox.Rect
определены в таком виде:
type Face struct {
Rect Rect
ID string
Name string
Matched bool
}
type Rect struct {
Top, Left int
Width, Height int
}
Структура Rect
определяет размеры лица на исходном изображении и используется в структуре Face
.
Поля ID
, Name
и Matched
используются когда необходимо распознать кто изображен на картинке. Но мы не будем использовать эту возможность в нашем приложении.
facebox.Rect
предоставляет значения высоты и ширины прямоугольника, но для image.Rect
нам нужно знать значения координат x1, y1 — x2, y2. Поэтому вы выполняем несложное преобразование face.Rect.Left+face.Rect.Width
и face.Rect.Top+face.Rect.Height
.
После этого опять используем функцию draw.Draw
для рисования черных прямоугольников поверх нового изображения. &image.Uniform{color.Black}
позволяет получить черное изображение.
В итоге у нас получается новое изображение.
Обратите внимание, что вам нужно будет импортировать github.com/machinebox/sdk-go/facebox
и несколько дополнительных пакетов.
Консольная утилита
Чтобы пользоваться всем этим кодом, который мы написали, необходимо добавить несколько функций для загрузки исходных изображений и сохранения результатов. Напишем функцию main
в которой объединим все наши наработки.
Использование нашей утилиты должно быть простым. В качестве аргумента будет указываться путь к исходному изображению.
Ниже код, который работает с аргументом строки, открывает картинку и использует defer для закрытия файла.
func main() {
if len(os.Args) < 2 {
log.Fatalln("usage: anon <image>")
}
filename := os.Args[1]
f, err := os.Open(filename)
if err != nil {
log.Fatalln(err)
}
defer f.Close()
Затем создаем клиента с помощью которого можно работать с Facebox сервисом:
fb := facebox.New("http://localhost:8080")
facebox.New
возвращает экземпляр facebox.Client
у которого есть целый набор методов. С помощью этих методов можно выполнять HTTP запросы к локальному Facebox докер контейнеру.
Для обнаружения лиц нам нужно вызвать метод Check
:
log.Println("Detecting faces...")
faces, err := fb.Check(f)
if err != nil {
log.Fatalln(err)
}
Переменная faces
это слайс объектов facebox.Face
которые будут использоваться для анонимизации.
Дальше нам нужно преобразовать исходное изображение в экземпляр image.Image
. С объектом такого типа мы выполнять все необходимые операции.
Так как метод Check
принимает на io.Reader
и читает данные из файла, то нам необходимо переместиться в начало файла:
_, err = f.Seek(0, os.SEEK_SET)
if err != nil {
log.Fatalln(err)
}
Это не самый красивый код. Мы перемещаемся в начало файла указывая позицию 0. os.SEEK_SET
означает что мы хотим установить позицию(в том смысле, что это не относительная операция смещения).
Если мы не использовали бы os.File
, то нам пришлось бы использовать что-то что поддерживает интерфейс io.ReadSeeker
. Как вариант, можно было бы прочитать все содержимое файла в bytes.Buffer
.
Теперь нам нужно декодировать набор байтов в объект image.Image
:
srcImage, _, err := image.Decode(f)
if err != nil {
log.Fatalln(err)
}
Функция image.Decode
декодирует данные из файла автоматически в нужный формат. Для поддержки большого количества форматов, нам нужно импортировать дополнительные пакеты.
Кроме импорта пакетов ничего больше делать не нужно. Каждый пакет в функции init
регистрирует функцию image.Decode
. Но в Go нельзя просто так импортировать пакет и не использовать его. Чтобы наша программа компилировалась, используем небольшой хак - укажем в качестве алиасов символ подчеркивания.
В результате секция с импортируемыми пакетами будет выглядеть примерно так:
import (
_ "image/gif"
_ "image/jpeg"
_ "image/png"
)
Теперь у нас есть объект image.Image
и мs моем использовать его как аргумент для вызова функции anonymise
вместе с слайсом faces
:
dstImage := anonymise(srcImage, faces)
Если все прошло хорошо, то необходимо сохранить dstImage
на диск в новый файл. Финальный код main
нашей утилиты:
// fudge the filename to add the -anon suffix (before the ext)
filename = filepath.Base(filename)
ext := filepath.Ext(filename)
dstFilename := filename[:len(filename)-len(ext)] + "-anon" + ext
dstFile, err := os.Create(dstFilename)
if err != nil {
log.Fatalln(err)
}
defer dstFile.Close()
log.Println("Saving image to " + dstFilename + "...")
err = jpeg.Encode(dstFile, dstImage, &jpeg.Options{Quality: 100})
if err != nil {
log.Fatalln(err)
}
log.Println("Done.")
Видно, что добавляется суффикс -anon
для всех новых изображений. Вызов jpeg.Encode
позволит сохранить все у нас получилось как JPEG изображение. Также, вы можете использовать png.Encode
для создания PNG изображений.
Замечу, что мы используем только пакет jpeg
и все остальные пакеты нам нужно удалить.
Тестируем программу
Мы закончили с кодом и теперь можем проверить как работает наша программа. Для тестирования нам нужна фотография с лицами людей. Запускаем утилиту и указываем путь к картинке в аргументе:
go run main.go family.jpg
Вывод программы будет примерно таким:
matryer$ go run main.go testdata/thebeatles.jpg
2017/05/06 00:50:53 Detecting faces...
2017/05/06 00:50:54 Saving image to thebeatles-anon.jpg...
2017/05/06 00:50:54 Done.
В сгенерированном файле все лица будут зацезурированны.
Заключение
С использованием MachineBox у нас получилось написать консольную утилиту для анонимизированния фотографий. При этом, у нас совсем немного кода(в основном это шаблонный код загрузки/сохранения/кодирования/декодирования и т.д.).
Что дальше
-
Посмотреть исходники этого примера.
-
Почитать как еще можно использовать Facebox, обучать его и использовать через Go SDK
-
Можно зафоловить @matryer в твиттере и следить за новыми статьями. И не забывайте про твиттер @machineboxio