Документ был создан с помощью программы leanote
. Лучшее чтение доступно на сайте блога.
SeetaFace6
— это последняя версия системы технологий от компании Zhongke Situo. Эта версия является открытой и бесплатной для использования всеми желающими. Она включает модули распознавания лиц, проверки живости, атрибутивного анализа и оценки качества.
В этом руководстве мы будем использовать SeetaFace
для объяснения, также будут даны некоторые выводы и рекомендации по использованию общих алгоритмов. Надеемся, что это поможет читателям, использующим SeetaFace
или другие продукты распознавания лиц.
В данном руководстве не будет затронут процесс компиляции и отладки кода. Будут указаны ключевые места, где могут возникнуть проблемы, чтобы разработчики могли проверить, совпадают ли они со своими проблемами.
Это руководство предназначено для людей, имеющих базовое понимание C++, способных анализировать основные фрагменты кода и успешно компилировать его на соответствующей платформе.
Предварительные знания включают базовое использование библиотеки OpenCV
и понимание некоторых основных принципов работы с изображениями. Все эти элементы являются вызовами API, поэтому даже если вы никогда ранее не работали с ними, это не повлияет на ваше понимание данного руководства.Библиотека OpenCV
, используемая в этом руководстве, имеет версию 2
или 3
.
Если у вас есть вопросы относительно самого языка C++, но вы уже знакомы с вызовами библиотек, то это руководство может вам не помочь напрямую. Мы рекомендуем изучить язык C++ как предварительный курс перед началом этого руководства. Также мы постараемся максимально учитывать начинающих программистов на C++.
Чтобы избежать длинных фрагментов кода, которые могут усложнять чтение, некоторые обязательные фрагменты кода будут помещены в раздел "Код" приложения. Эти полные фрагменты кода помогут читателю лучше понять основные настройки и принципы работы SeetaFace
, основываясь только на информации, представленной в документе. Конечно, пропустив эти сложные фрагменты кода, вы всё равно сможете получить необходимое понимание и использовать систему.
Для удобства чтения, фрагменты кода в документе были сокращены и адаптированы, сохраняя семантическую целостность. Реализация кода должна быть проверена на основе окончательно загруженного кода.
Чтобы учесть различные уровни понимания, будут даны объяснения некоторых базовых концепций.Разработчики, уже знакомые с этими концепциями благодаря своему опыту в области компьютерного зрения или искусственного интеллекта, могут пропустить соответствующие разделы, но должны обратиться к ним, если возникают какие-либо недоразумения. Из-за большого количества кода в документе, используются английские слова. При этом объяснения даются на китайском языке. Из-за ограничений автора, возможно, не все английские и китайские ключевые слова будут полностью объяснены и соответствовать друг другу. К счастью, английские слова, используемые в коде, являются общими и их прямой перевод на китайский язык соответствует ключевым словам контекста.Примечание: перед чтением данного документа рекомендуется подготовить среду компиляции, чтобы избежать проблем при компиляции и запуске.
Как часть C++ библиотеки для работы с изображениями, структура данных для хранения изображений является базовой. В интерфейсе объект изображения представлен как SeetaImageData
.
struct SeetaImageData {
int width; // Ширина изображения
int height; // Высота изображения
int channels; // Количество каналов
unsigned char *data; // Данные изображения
};
Здесь важно отметить формат хранения данных data
. Это последовательность значений пикселей, представленных 8-битными беззнаковыми целыми числами, организованных в порядке [высота, ширина, количество каналов]
. Для цветных изображений каналы располагаются в порядке BGR (синий, зелёный, красный).
На следующем рисунке показана структура памяти для цветного изображения размером 4x3 пикселей.
Это непрерывное хранение в памяти. Таким образом, объём памяти, занимаемый данными изображения data
, равен 4*3*3=36
байтам.> Примечание: формат BGR является стандартным для OpenCV. Часто можно встретить прямое присваивание data
из cv::Mat
к data
из SeetaImageData
.
Тип данных
data
— этоuint8
, диапазон значений —[0, 255]
, что представляет собой градацию от самого темного до самого светлого тона. На платформах, где используется типfloat
для представления градации, этот диапазон эквивалентен[0, 1]
.Детальное описание этого формата необходимо для обеспечения совместимости с различными библиотеками изображений. Здесь следует обратить внимание на два аспекта: 1. тип данныхuint8
и диапазон значений[0, 255]
; 2. порядок каналов BGR, который отличается от RGB или RGBA (где A — уровень прозрачности).
Примечание: порядок каналов может влиять на точность алгоритма распознавания, поэтому при конвертации между различными библиотеками следует проявлять осторожность.
Этот чисто C-интерфейс не всегда удобен для управления ресурсами. Библиотека SeetaFace предоставляет упакованные версии SeetaImageData
в виде seeta::ImageData
и seeta::cv::ImageData
.
Ниже приведён пример преобразования изображения, загруженного с помощью OpenCV, в структуру SeetaImageData
.
cv::Mat cvimage = cv::imread("1.jpg", cv::IMREAD_COLOR);
SeetaImageData simage;
simage.width = cvimage.cols;
simage.height = cvimage.rows;
simage.channels = cvimage.channels();
simage.data = cvimage.data;
Примечание: использование
simage.data
, напрямую присвоенного изcvimage.data
, может вызвать проблемы, если данные изcvimage
будут удалены после завершения выполнения программы.data
получает временные данные, взятые "в аренду". В течение периода тестирования необходимо гарантировать, что объектcvimage
не будет освобожден или формально обновлен.> **Примечание:** Исходное изображение может быть пустым. Для проверки корректности входных данных следует использовать метод `cvimage.empty()`, чтобы определить, является ли изображение пустым. Здесь используется `cv::imread` с вторым параметром, установленным в `cv::IMREAD_COLOR`. Получаемый объект типа `cv::Mat` имеет непрерывно хранящиеся данные, что обязательно при использовании `SeetaImageData`. Если вы не уверены, являются ли данные непрерывно хранящимися, можно использовать следующий фрагмент кода для преобразования:
cpp if (!cvimage.isContinuous()) cvimage = cvimage.clone();
Конечно, преобразование между объектами типов `cv::Mat` и `seeta::ImageData` также возможно в обратном направлении.
```cpp
cv::Mat another_cvimage = cv::Mat(simage.height, simage.width, CV_8UC(simage.channels), simage.data);
seeta::ImageData
и seeta::cv::ImageData
также представляют собой упаковку базовых типов и включают управление жизненным циклом объектов.
Вот пример такой упаковки:
namespace seeta {
namespace cv {
// using namespace ::cv;
class ImageData : public seeta::SeetaImageData {
public:
ImageData(const ::cv::Mat& mat)
: cv_mat(mat.clone()) {
this->width = cv_mat.cols;
this->height = cv_mat.rows;
this->channels = cv_mat.channels();
this->data = cv_mat.data;
}
private:
::cv::Mat cv_mat;
};
}
}
Таким образом, использование seeta::cv::ImageData
можно упростить до:
seeta::cv::ImageData image_data = cv::imread("1.jpg");
Поскольку seeta::cv::ImageData
наследует от seeta::SeetaImageData
, его можно передать как объект типа const seeta::SeetaImageData&
там, где это требуется.
Примечание: Без специальных указаний все входные изображения модулей
SeetaFace
должны иметь цветовое изображение с каналами BGR.
Ссылка:
CStruct.h
,Struct.h
иStruct_cv.h
.
После представления изображения нам также необходимо представить некоторые геометрические фигуры на нем, такие как прямоугольники и точки, используемые для представления положения лица и координат ключевых точек лица.Для этого мы используем экранную систему координат, где левый верхний угол служит началом координат, горизонтальное направление слева направо — осью X, а вертикальное направление сверху вниз — осью Y. Координаты измеряются в пикселях.
Значения координат могут быть отрицательными и представлять собой вещественные числа, что указывает на части экрана за его границами и координаты между пикселами соответственно. С учетом вышеописанной информации мы можем определить прямоугольники, точки и другие объекты.
/**
* Прямоугольник
*/
struct SeetaRect {
int x; // Координата X верхнего левого угла
int y; // Координата Y верхнего левого угла
int width; // Ширина прямоугольника
int height; // Высота прямоугольника
};
/**
* Размеры
*/
struct SeetaSize {
int width; // Ширина
int height; // Высота
};
/**
* Точка
*/
struct SeetaPointF {
double x; // Координата X
double y; // Координата Y
};
Внимание: По традиционному способу представления координат и размеров обычно ширина и координата X располагаются перед высотой и координатой Y. Это отличается от представления в памяти изображения, где высота располагается перед шириной. При преобразовании координат в смещение в памяти следует это учитывать.#### 1. 2. 2 Пример использования Эта базовая структура данных используется после её определения. Здесь приведён пример того, как можно использовать эти данные для отрисовки на изображении. Для начала предположим определение изображения, прямоугольника и точки.
cv::Mat canvas = cv::imread("1.jpg");
SeetaRect rect = {20, 30, 40, 50};
SeetaPointF point = {40, 55};
При наличии значений, имеющих смысл, можно начать отрисовку.
// Отрисовка прямоугольника
cv::rectangle(canvas, cv::Rect(rect.x, rect.y, rect.width, rect.height), CV_RGB(128, 128, 255), 3);
// Отрисовка точки
cv::circle(canvas, cv::Point(point.x, point.y), 2, CV_RGB(128, 255, 128), -1);
См. также
CStruct.h
иStruct.h
.### 1.3 Настройка модели В наборе алгоритмов помимо библиотеки кода есть также файлы данных, которые мы называем моделями. Вот основные определения настроек модели дляSeetaFace
:
enum SeetaDevice {
SEETA_DEVICE_AUTO = 0,
SEETA_DEVICE_CPU = 1,
SEETA_DEVICE_GPU = 2,
};
struct SeetaModelSetting {
enum SeetaDevice device;
int id; // когда устройство — GPU, id представляет номер GPU
const char **model; // массив строк модели, завершающийся нулём
};
Необходимо отметить, что поле model
представляет собой массив C-стиля, заканчивающийся нулём. Пример инициализации может выглядеть так: установка модели fr_model.csta
, работающей на CPU:0.
SeetaModelSetting setting;
setting.device = SEETA_DEVICE_CPU;
setting.id = 0;
setting.model = {"fr_model.csta", NULL};
Конечно, библиотека SeetaFace
также предоставляет способ установки модели на C++, который наследуется от родительского класса. Подробная реализация представлена в блоках кода приложения:
class seeta::ModelSetting : public SeetaModelSetting;
Реализация кода находится в файле Struct.h
, а метод использования выглядит следующим образом:
seeta::ModelSetting setting;
setting.append("fr_model.csta");
С использованием общего подхода к настройке моделей, обычно говорят о "загрузке модели распознавания лиц". Процесс загрузки модели всегда начинается с создания объекта SeetaModelSetting
, передачи ему соответствующих алгоритмических данных.
См. также:
CStruct.h
иStruct.h
.### 1.4 Объектный жизненный цикл
Объектный жизненный цикл является очень важной концепцией в C++. Он служит основой для метода RAII (Resource Acquisition Is Initialization).
Пример использования объекта seeta::FaceRecognizer
демонстрирует два возможных способа его создания:
seeta::FaceRecognizer FR(setting);
или
seeta::FaceRecognizer* pFR = new seeta::FaceRecognizer(setting);
При втором варианте после завершения работы с объектом pFR
следует вызвать delete
для освобождения ресурсов:
delete pFR;
Первый вариант создаёт объект FR
. Когда управление выходит за рамки области объявления этого объекта (обычно это правая скобка), он автоматически освобождается.
Основные понятия должны быть легко поняты читателями. Далее будут рассмотрены некоторые специфические характеристики объектного жизненного цикла в SeetaFace
.
1. Объекты типа seeta::FaceRecognizer
не могут быть скопированы или присвоены:
seeta::FaceRecognizer FR1(setting);
seeta::FaceRecognizer FR2(setting);
seeta::FaceRecognizer FR3 = FR1; // Ошибка
FR2 = FR1; // Ошибка
Поэтому передача объекта типа seeta::FaceRecognizer
по значению в функцию запрещена. В случае необходимости передачи объекта следует использовать указатель на объект.
Эта особенность обеспечивается для того, чтобы не открывать детали реализации и гарантировать совместимость ABI (Application Binary Interface).
2. Указатели на "заемные" объекты не требуют освобождения.Например, интерфейс для детектора лиц может выглядеть так:
struct SeetaFaceInfo {
SeetaRect pos;
float score;
};
struct SeetaFaceInfoArray {
struct SeetaFaceInfo* data;
int size;
};
SeetaFaceInfoArray FaceDetector::detect(const SeetaImageData& image) const;
Здесь поле data
структуры SeetaFaceInfoArray
представляет собой "заемный" указатель, который не требуется освобождать вне зависимости от контекста.
```Не требуются явные указания на освобождение памяти для интерфейсов, где это не указано. В чистых C-интерфейсах возвращаемые указатели на объекты являются новыми объектами, поэтому требуется соответствующий метод освобождения объекта.
3. Создание и освобождение объектов могут быть затратными операциями. Для сценариев, где часто используются новые объекты, рекомендуется использовать пулы объектов для повторного использования объектов.
### 1.5 Поточная безопасность
Поточная безопасность является важной характеристикой при разработке программного обеспечения. Однако понятие поточной безопасности может иметь различные интерпретации в разных контекстах. Чтобы избежать недопониманий, здесь приведены несколько примеров использования для иллюстрации этой концепции.
1. Объекты можно передавать между потоками. Объект, созданный в потоке 1, может использоваться в потоке 2.
2. Конструкторы объектов могут выполняться параллельно, то есть несколько потоков могут одновременно создавать объекты.3. Вызовы интерфейса одного объекта не должны выполняться параллельно; использование одного и того же объекта несколькими потоками одновременно запрещено.
Конечно, некоторые специальные объекты могут иметь более высокую степень поточной безопасности. Например, вызовы интерфейса `seeta::FaceDatabase` могут выполняться параллельно, но вычисления не будут выполняться параллельно.
## 2. Распознавание лиц и локализация ключевых точек
Наконец, после подробного объяснения базовых особенностей, мы переходим к двум важным модулям распознавания: распознаванию лиц и локализации ключевых точек.
**Распознавание лиц**, `seeta::FaceDetector`: принимает изображение для анализа и выводит положение каждого лица в виде прямоугольника.
**Локализация ключевых точек**, `seeta::FaceLandmarker`: принимает изображение для анализа и положение лица для анализа, а затем выводит координаты N ключевых точек (внутри изображения).
Эти два модуля отвечают за поиск подходящих областей для анализа лиц и локализацию ключевых точек для определения состояния лица, что позволяет легко выполнять последующий анализ и сопоставление.
### 2.1 Распознаватель лиц
Эффективность распознавателя лиц показана на следующем рисунке:

Здесь представлены основные интерфейсы распознавателя лиц:```cpp
namespace seeta {
class FaceDetector {
FaceDetector(const SeetaModelSetting &setting);
SeetaFaceInfoArray detect(const SeetaImageData &image) const;
std::vector<SeetaFaceInfo> detect_v2(const SeetaImageData &image) const;
void set(Property property, double value);
double get(Property property) const;
}
}
Пример создания нового распознавателя лиц:
#include <seeta/FaceDetector.h>
seeta::FaceDetector* new_fd() {
seeta::ModelSetting setting;
setting.append("face_detector.csta");
return new seeta::FaceDetector(setting);
}
Имея детектор, мы можем анализировать изображение для выявления лиц, а также использовать функцию для обнаружения всех лиц в изображении и вывода координат:
#include <seeta/FaceDetector.h>
void detect(seeta::FaceDetector *fd, const SeetaImageData &image) {
std::vector<SeetaFaceInfo> faces = fd->detect_v2(image);
for (auto &face : faces) {
SeetaRect rect = face.pos;
std::cout << "[" << rect.x << ", " << rect.y << ", "
<< rect.width << ", " << rect.height << "]: "
<< face.score << std::endl;
}
}
Обратите внимание, что обычно все обнаруженные лица сортируются по уровню уверенности. Когда приложение требует получить самое большое лицо, можно выполнить частичную сортировку для получения максимального размера лица. После выполнения следующего кода, faces[0]
будет представлять положение самого большого лица.
std::partial_sort(faces.begin(), faces.begin() + 1, faces.end(), [](SeetaFaceInfo a, SeetaFaceInfo b) {
return a.pos.width > b.pos.width;
});
Детектор лиц может настраиваться через метод set
. Доступные свойства включают:
seeta::FaceDetector::PROPERTY_MIN_FACE_SIZE минимальный размер лица
seeta::FaceDetector::PROPERTY_THRESHOLD пороговое значение детектора
seeta::FaceDetector::PROPERTY_MAX_IMAGE_WIDTH максимальная ширина изображения
seeta::FaceDetector::PROPERTY_MAX_IMAGE_HEIGHT максимальная высота изображения
```Минимальный размер лица — это часто используемый параметр для детектора лиц, значение по умолчанию равно `20`, единицы измерения — пиксели. Этот параметр указывает минимальный размер лица, который может быть обнаружен на входном изображении. Обратите внимание, что этот размер не является строгим значением пикселей; например, если минимальный размер лица установлен равным `80`, то обнаружение лица шириной `75` пикселей считается нормальным. Это значение представляет нижний порог способностей детектора.
Минимальный размер лица тесно связан с производительностью детектора. Основной аспект — скорость. В рекомендациях по использованию мы советуем установить этот параметр как можно выше в рамках конкретного приложения. `SeetaFace` использует метод обучения `Regression BindingBox`. Если параметр минимального размера лица установлен равным `80`, то исходное изображение может быть уменьшено до четвертой части своего первоначального размера. Это позволяет значительно ускорить процесс обработки данных, увеличивая скорость до 16 раз по сравнению со значением минимального размера лица `20`.Пороговое значение детектора имеет значение по умолчанию `0.9`, допустимый диапазон `[0, 1]`. Обычно это значение не требует изменения, за исключением случаев обработки крайних ситуаций. Чем меньше это значение, тем ниже вероятность пропуска объекта, но повышается вероятность ошибочного обнаружения. `Максимальная допустимая ширина изображения` и `максимальная допустимая высота изображения` — это настройки, связанные между собой, с начальным значением равным `2000`. Эти максимальные значения высоты и ширины представляют реальные размеры, используемые алгоритмом при детекции. Детектор поддерживает динамическое вводимое изображение; однако увеличение размера входного изображения приводит к увеличению потребляемой памяти и времени вычислений. Без ограничений одно очень высокого разрешения изображение может легко заполнить всю доступную память. Ограничение состоит в том, что если ширина или высота входного изображения превышает установленное значение, то изображение будет автоматически масштабировано до этого лимита.Конечно, мы хотели бы, чтобы алгоритмы работали хорошо во всех условиях, но естественные законы гораздо сложнее, чем можно было бы представить простым несколькими мегабайтным файлом. В применении всегда требуется компромисс, который называется `trade-off`.
### 2.2 Локатор ключевых точек лица
Эффект работы локатора ключевых точек показан на следующем рисунке:

Локатор принимает на вход исходное изображение и результаты детекции лица, а затем выводит координаты ключевых точек для указанного лица.
Обнаруженные 5 точек расположены последовательно: центр левого глаза, центр правого глаза, кончик носа, левый угол рта и правый угол рта. Здесь важно отметить, что указание «левый» и «правый» относится к положению этих частей лица на изображении, а не к положению человека на изображении, то есть центр левого глаза — это центр левого глаза на изображении.
Аналогичным образом можно создать локатор ключевых точек:
```cpp
#include <seeta/FaceLandmarker.h>
seeta::FaceLandmarker* new_fl() {
seeta::ModelSetting setting;
setting.append("face_landmarker_pts5.csta");
return new seeta::FaceLandmarker(setting);
}
```
Код для печати координат ключевых точек на основе лицовой детекции представлен ниже:
```cpp
#include <seeta/FaceLandmarker.h>
void mark(seeta::FaceLandmarker* fl, const SeetaImageData& image, const SeetaRect& face) {
std::vector<SeetaPointF> points = fl->mark(image, face);
for (auto& point : points) {
std::cout << "[" << point.x << ", " << point.y << "]" << std::endl;
}
}
```Открытая версия также предоставляет модели для большего количества точек, но из-за ограничений по объему место, подробное описание расположения точек здесь отсутствует.
Например, модель `face_landmarker_pts68.csta` предназначена для детекции 68 ключевых точек. Координаты этих точек могут быть распечатаны по одному для их различения.
Необходимо подчеркнуть, что эти ключевые точки представляют координаты ключевых областей лица. В некоторых случаях они также могут называться `ключевыми точками`, но это никак не связано с понятием ключевых точек в контексте распознавания лиц. Нет утверждения, что более большое количество ключевых точек приведет к повышению точности распознавания лиц. Основные точки лица и другие анализы, основанные на распознавании лица, используют пятиточечное выравнивание. После того как алгоритмическая последовательность определена, можно использовать только пятиточечное выравнивание. Это выравнивание является предварительной информацией для дальнейших алгоритмических шагов и не может быть заменено.
По опыту, пятиточечное выравнивание достаточно для удовлетворения требований к точности распознавания лиц или других связанных анализов. Увеличение количества ключевых точек просто усложняет метод, но не оказывает прямого влияния на конечный результат.> Ссылка: `seeta/FaceDetector.h` `seeta/FaceLandmarker.h`
## 3. Выделение и сравнение черт лица
Эти две важнейшие функции предоставляются модулем `seeta::FaceRecognizer`. Методы выделения черт и их сравнения взаимосвязаны.
Это базовая концепция распознавания лиц, которая заключается в преобразовании входящего изображения лица в двоичные данные черт, а затем в использовании этих черт для расчета степени схожести, после чего сравнивают её со значением пороговой схожести. Обычно, если значение превышает порог, то считается, что это одно и то же лицо.

Черты в `SeetaFace` представлены массивами типа `float`, а метод сравнения черт основан на скалярном произведении векторов.
### 3.1 Выделение черт лица
Сначала создаем объект распознавания лиц:
```cpp
#include <seeta/FaceRecognizer.h>
seeta::FaceRecognizer* new_fr() {
seeta::ModelSetting setting;
setting.append("face_recognizer.csta");
return new seeta::FaceRecognizer(setting);
}
```
Процесс выделения черт состоит из двух этапов: 1) вырезание области лица на основе пяти ключевых точек; 2) применение этой области к сети выделения черт для получения черт.
Эти два этапа могут быть вызваны отдельно или объединены в одном методе.
Два этапа соответственно реализованы через методы `CropFaceV2` и `ExtractCroppedFace` класса `seeta::FaceRecognizer`. Также можно использовать метод `Extract` для выполнения обоих этапов за один вызов.Пример использования метода `Extract` для выделения черт:
```cpp
#include <seeta/FaceRecognizer.h>
#include <memory>
std::shared_ptr<float> extract(
seeta::FaceRecognizer* fr,
const SeetaImageData& image,
const std::vector<SeetaPointF>& points)
{
std::shared_ptr<float> features(new float[fr->GetExtractFeatureSize()], std::default_delete<float[]>());
fr->Extract(image, points.data(), features.get());
return features;
}
```
Аналогично можно предоставить функцию для вычисления коэффициента схожести:
```cpp
#include <seeta/FaceRecognizer.h>
#include <memory>
``````markdown
const std::shared_ptr<float>& feat2) {
return fr->CalculateSimilarity(feat1.get(), feat2.get());
}
```
> **Внимание:** здесь количество ключевых точек в `points` должно быть равно 5 ключевым точкам, извлеченным с помощью `SeetaFace`.
Длина извлеченных признаков может различаться в зависимости от модели. Для получения текущей длины извлеченных признаков используйте метод `GetExtractFeatureSize`.
Диапазон значений схожести составляет [0, 1], однако стоит отметить, что если вычисление производится через скалярное произведение, то наличие комплексных чисел в характеристиках может привести к получению отрицательной величины схожести. Внутренне в распознавателе отрицательные значения преобразуются в ноль.
В некоторых специальных случаях требуется разделение процесса извлечения признаков на два этапа, например, обработка изображения на клиентской стороне и извлечение признаков и сравнение на серверной. Ниже представлен пример такого двухэтапного извлечения признаков:
```cpp
#include <seeta/FaceRecognizer.h>
#include <memory>
std::shared_ptr<float> extract_v2(
seeta::FaceRecognizer* fr,
const SeetaImageData& image,
const std::vector<SeetaPointF>& points) {
std::shared_ptr<float> features(
new float[fr->GetExtractFeatureSize()],
std::default_delete<float[]>());
seeta::ImageData face = fr->CropFaceV2(image, points.data());
fr->ExtractCroppedFace(face, features.get());
return features;
}
```
```Объекты `face` и `features`, созданные временно внутри функции, имеют фиксированную размерность после загрузки распознавателя, поэтому эти объекты могут быть переиспользованы.
Отдельно следует отметить, что если требуется извлечение признаков только для самого большого лица на изображении, можно использовать следующий подход:
```cpp
std::shared_ptr<float> extract(
seeta::FaceDetector* fd,
seeta::FaceLandmarker* fl,
seeta::FaceRecognizer* fr,
const SeetaImageData& image) {
auto faces = fd->detect_v2(image);
if (faces.empty()) return nullptr;
std::partial_sort(faces.begin(), faces.begin() + 1, faces.end(),
[](SeetaFaceInfo a, SeetaFaceInfo b) {
return a.pos.width > b.pos.width;
});
auto points = fl->mark(image, faces[0].pos);
return extract(fr, image, points);
}
```
### 3.2 Сравнение лицевых признаков
В предыдущем разделе мы уже рассмотрели методы извлечения и сравнения признаков. Здесь мы подробно рассмотрим методы вычисления схожести признаков.
Ниже приведён способ сравнения признаков, реализация на C:
```cpp
float compare(const float* lhs, const float* rhs, int size) {
float sum = 0;
for (int i = 0; i < size; ++i) {
```
Метод сравнения характеристик на основе скалярного произведения можно оптимизировать с помощью общих математических методов, таких как `gemm` или `gemv`.### 3.3 Характеристики различных моделей распознавания
Файл `SeetaFace6` включает три модели распознавания:
| Файл | Длина характеристики | Общий порог | Описание |
|-----------------------|----------------------|-------------|--------------------------------------------------------------------------|
| face_recognizer.csta | 1024 | 0.62 | Универсальная модель распознавания лиц для высокоточных сценариев |
| face_recognizer_mask.csta | 512 | 0.48 | Модель распознавания лиц при использовании масок |
| face_recognizer_light.csta | 512 | 0.55 | Лёгкая модель распознавания лиц |
Общий порог представляет собой рекомендованное значение для использования в обычных сценариях. В случае одиночной проверки (1:1) этот порог может оказаться слишком низким, а в случае множественной проверки (1:N) — слишком высоким.
Важно отметить, что характеристики, полученные различными моделями, не сравнимы друг с другом, даже если они имеют одинаковое имя. При замене модели распознавания в работающей системе все фотографии базы данных должны быть переобработаны для получения новых характеристик.
### 3.4 О схожести и порогах
В разделе **3.3** мы представили общий порог для различных моделей. Порог служит критерием принятия решения о том, является ли найденный объект одним и тем же лицом. Если схожесть двух характеристик превышает порог, то лица считаются одним и тем же.Этот порог и соответствующий ему уровень схожести являются критериями для алгоритма, который использует статистическую информацию для определения того, является ли лицо одним и тем же. Это может звучать абстрактно, но вот конкретный пример: схожесть 0.5 при пороге 0.49 указывает на то, что лица считаются одним и тем же. Однако это не означает, что два лица имеют половину одинаковых черт. Аналогично, схожесть 100% не говорит о полной идентичности двух лиц, без учета временных изменений.
Однако значение 0.5 указывает на то, что лица считаются одним и тем же. По общему мнению, лица считаются одним и тем же при схожести около 80%. В этом случае обычно применяется преобразование схожести, чтобы гарантировать, что любое совпадение будет иметь схожесть выше 0.8.
Итак, схожесть, выведенная алгоритмом распознавания, становится бессмысленной без учета порога. Качество алгоритма зависит от его способности различать различные образцы и использовать порог для разделения положительных и отрицательных образцов.
Для сравнения точности различных алгоритмов распознавания обычно используется график ROC (Receiver Operating Characteristic), который показывает производительность алгоритма при различных значениях порога. В этом случае, даже если используются методы преобразования схожести, любое положительно коррелированное отображение приведёт к полностью одинаковым кривым ROC.Далее следует объяснение одного неправильного способа тестирования. Часто люди задают вопрос, почему алгоритм A работает хуже, чем алгоритм B, когда сравниваются две фотографии одного и того же человека, и алгоритм A выдает меньшую степень схожести, чем алгоритм B. Конечно, "эффективность" зависит от многих факторов, таких как необходимость получения очень высокой степени схожести при распознавании одного и того же лица. Однако после вышеупомянутого обсуждения, мы надеемся, что читатель сможет осознать односторонний характер этого подхода к тестированию точности.
### 3.5 1 против 1 и 1 против N
Обычно в системах распознавания лиц можно различать два типа: 1 против 1 и 1 против N. Некоторые считают, что 1 против 1 является частным случаем 1 против N при N = 1. Здесь мы не будем подробно рассматривать различия между этими двумя типами. В реальных применениях важно адаптироваться к конкретной ситуации, а не применять универсальные шаблоны. Мы представляем общие способы использования этих двух типов.
Общий случай 1 против 1 — это сравнение образца с известным источником (например, фотография на удостоверении личности) и текущей фотографией. Это используется для проверки личности, например, при использовании карты доступа или других средств идентификации. Таким образом, шире говоря, это может относиться к ситуациям, где сотрудник использует карту доступа и затем проходит через систему распознавания лиц; или когда личный аккаунт требует распознавания лица вместо пароля. Все эти случаи, где известна личность пользователя, и требуется его идентификация на месте, могут быть отнесены к категории Yöntem 1 против 1.
Ситуация 1 против N отличается тем, что для каждого нового лица, которое должно быть идентифицировано, нет информации о его личности. Его необходимо найти среди базы данных. Если лицо найдено в базе данных, система сообщает об успешной идентификации. Если лицо не найдено, система сообщает об ошибке. Если бизнес-процесс гарантирует, что лицо обязательно находится в базе данных, это будет закрытым набором тестов, также называемым поиском лица. В противном случае, это будет открытым набором тестов.
Из описания видно, что открытый набор тестов более сложен, так как он требует учета вероятности ошибочного распознавания.
При работе с 1 против N, сначала все лица в базе данных преобразуются в характеристики, затем характеристика нового лица, зафиксированного камерой, сравнивается со всеми характеристиками в базе данных. Если степень схожести превышает пороговое значение, распознавание считается успешным. В противном случае, новое лицо не найдено в базе данных.Часто встречающийся пример 1 против N — это динамическое распознавание лиц с помощью камеры. В этом случае важны модули «отслеживания лица» и «оценки качества». Эти два метода используются в динамическом распознавании лиц для решения проблем с производительностью и точностью. В системах динамического распознавания лиц часто наблюдается движение человека перед камерой, который остаётся в поле зрения некоторое время. В этом случае требуется отслеживание, чтобы определить, что перед камерой находится человек. На основе этого одного и того же человека можно идентифицировать только один раз.Используемый для идентификации образец фотографии выбирается с помощью модуля оценки качества, чтобы выбрать наиболее подходящий для идентификации снимок.
Комбинированное использование методов "отслеживания лица" и "оценки качества" позволяет избежать нагрузки на систему при анализе каждого кадра, а также повышает точность идентификации за счет выбора лучшего из нескольких кадров.
### 3.6 Оптимизация распознавания лиц
В разделе 3.5 было представлено базовое выполнение метода 1 против N, а также обсуждено влияние пороговых значений на ошибочное распознавание и общую точность. Это свойство может привести к ситуации, когда нам придется регулировать пороговые значения для достижения желаемого результата. Однако это требует более глубокого подхода — внедрения важной части системы: базового набора данных (библиотеки).
Качество изображений в базовой библиотеке обычно определяет общую производительность системы. Для базовой библиотеки обычно существуют два требования: 1) качество изображений должно быть как можно выше; они должны представлять собой четкие, хорошо освещённые, естественно выраженные лица без помех, размером примерно 128x128 пикселей, без шума и искажений; 2) изображения должны быть максимально похожими на условия эксплуатации.Два этих требования обычно учитываются вместе. Первое требование обычно выполняется путём использования свежих фотографий людей с хорошим качеством изображений и минимальными потерями при передаче. Второе требование обычно выполняется через процесс регистрации на месте, где используется тот же тип камеры, что и для распознавания.
При этом второе требование часто включает первое, что делает реальное применение более эффективным.
Кроме того, следует избегать следующих ситуаций: 1) искажение изображений лиц, такого как редактирование с использованием Photoshop или других программ; 2) использование макияжа. По сути, алгоритмы распознавания будут затруднены из-за макияжа. Исходя из двух принципов для создания базовой библиотеки, не обязательно требовать фотографий без макияжа, но важно, чтобы изображения были достаточно различимыми. То, чего люди сделать не могут, того не стоит требовать от компьютера.
Требования к базовым данным аналогичны требованиям к фотографиям с места событий. Это связано с тем, что источники двух сравниваемых характеристик симметричны.
Мы рассмотрим модуль оценки качества в последующих разделах, который предоставляет общую методику оценки качества. Этот модуль позволяет фильтровать изображения, не соответствующие требованиям распознавания, повышая таким образом общую производительность системы.> Примечание: `SeetaFace6` предоставляет полный пример реализации динамического распознавания лиц. Подробнее см. открытый выпуск платформы.
> Примечание: Для распознавания лиц используется более абстрактный интерфейс `seeta::FaceDatabase`. Подробное использование см. в открытом выпуске `SeetaFace2`.
> Ссылка: `seeta/FaceRecognizer.h`, `seeta/FaceDatabase.h`
## 4. Животропное распознавание
Перед тем как рассматривать важнейшие компоненты распознавания лиц — трекинг и оценку качества, мы рассмотрим модуль животропного распознавания, который играет ключевую роль в приложениях.
Значимость этого модуля здесь не будет подробно объясняться; вместо этого сразу приведены решения по животропному распознаванию в `SeetaFace`.
Животропное распознавание использует два подхода: глобальное животропное распознавание и локальное животропное распознавание.
Глобальное животропное распознавание выполняется путём анализа всего изображения, чтобы выявить потенциальные средства атаки, такие как мобильные телефоны, планшеты, фотографии и т.д.
Локальное животропное распознавание анализирует детали изображения лица с помощью алгоритма, различая однослойное и двухслойное изображение. В случае последнего считается, что произошла атака.
### 4.1 Базовое использованиеВ отличие от других модулей, распознаватель животропного распознавания может загружать модель локального распознавания или модель локального распознавания плюс модель глобального распознавания.Здесь загружена только одна модель локального распознавания:
```cpp
#include <seeta/FaceAntiSpoofing.h>
seeta::FaceAntiSpoofing* new_fas() {
seeta::ModelSetting setting;
setting.append("fas_first.csta");
return new seeta::FaceAntiSpoofing(setting);
}
```
Или модель локального распознавания плюс модель глобального распознавания, активирующая способность глобального распознавания:
```cpp
#include <seeta/FaceAntiSpoofing.h>
seeta::FaceAntiSpoofing* new_fas_v2() {
seeta::ModelSetting setting;
setting.append("fas_first.csta");
setting.append("fas_second.csta");
return new seeta::FaceAntiSpoofing(setting);
}
```
Вызовы осуществляются в двух режимах: одиночное распознавание кадра и видео распознавание.
Их интерфейсы представлены следующими декларациями:
```cpp
seeta::FaceAntiSpoofing::Status seeta::FaceAntiSpoofing::Predict(const SeetaImageData& image, const SeetaRect& face, const SeetaPointF* points) const;
```
Интерфейсы двух методов имеют одинаковую форму входных и выходных данных.
Перечислим объявление выходных данных:
```cpp
class FaceAntiSpoofing {
public:
/*
* Статус живого лица
*/
enum Status
{
REAL = 0, ///< Реальное лицо
SPOOF = 1, ///< Поддельное лицо (ложное)
FUZZY = 2, ///< Невозможно определить (низкое качество изображения лица)
DETECTING = 3 ///< В процессе проверки
};
};
```
Метод `одиночной идентификации` возвращает значение `REAL`, `SPOOF` или `FUZZY`.
Метод `идентификации видео` может вернуть значения `REAL`, `SPOOF`, `FUZZY` или `DETECTING`.Основное различие между двумя режимами работы заключается в том, что первый режим позволяет получить результат идентификации после анализа одного кадра, а второй требует нескольких кадров для получения результата. При недостаточном количестве кадров для идентификации видео, состояние будет равно `DETECTING`.Пример вызова одиночной идентификации:
```cpp
void predict(seeta::FaceAntiSpoofing *fas, const SeetaImageData &image, const SeetaRect &face, const SeetaPointF *points) {
auto status = fas->Predict(image, face, points);
switch(status) {
case seeta::FaceAntiSpoofing::REAL:
std::cout << "Реальное лицо" << std::endl; break;
case seeta::FaceAntiSpoofing::SPOOF:
std::cout << "Поддельное лицо" << std::endl; break;
case seeta::FaceAntiSpoofing::FUZZY:
std::cout << "Невозможно определить" << std::endl; break;
case seeta::FaceAntiSpoofing::DETECTING:
std::cout << "В процессе проверки" << std::endl; break;
}
}
```
Необходимо отметить, что `face` и `points` должны соответствовать друг другу, то есть `points` должны представлять ключевые точки лица, которое представлено `face`. `points` состоит из пяти ключевых точек. Разумеется, `image` представляет собой исходное изображение для идентификации.
Если используется режим **идентификации видео**, просто замените `fas->Predict(image, face, points)` на `fas->PredictVideo(image, face, points)` в методе `predict`.
При использовании режима **идентификации видео**, если требуется начать новый анализ видео после завершения текущего, следует использовать метод `ResetVideo` для сброса состояния идентификации перед новым анализом:
```cpp
void reset_video(seeta::FaceAntiSpoofing *fas) {
fas->ResetVideo();
}
```Понимание базовых интерфейсов вызова позволяет сразу видеть, что методы идентификации принимают данные о положении одного лица и его ключевых точках. Поэтому, когда видео или изображение содержат несколько лиц, бизнес-логика должна решить, какое конкретное лицо будет распознано. Обычно есть следующие варианты:1. Выполнять распознавание одного лица; если в кадре появляются два лица, процесс прекращается.
2. Распознавать самое большое лицо.
3. Распознавать лицо, которое находится в заранее определенной области.
Эти варианты мало влияют на точность распознавания, но отличаются по бизнес-требованиям и удобству использования.
### 4.2 Настройка параметров
Установка количества кадров видео:
```cpp
void SetVideoFrameCount(int32_t number);
```
Значение по умолчанию — 10. В режиме `PredictVideo`, после превышения этого значения `number`, можно получить результат распознавания. Это количество представляет собой число кадров, используемых для объединения результатов распознавания. При входе более чем за установленное количество кадров используется скользящий оконный метод, который возвращает объединенный результат последних входных кадров. Как правило, при значении меньше 10, увеличение количества кадров делает результат более стабильным, с лучшими относительными характеристиками производительности, но с большим отложением времени получения результата.Установка пороговых значений:
```cpp
void SetThreshold(float clarity, float reality);
```
Значение по умолчанию — `(0.3, 0.8)`. При живом распознавании, если значение чёткости (`clarity`) ниже порогового значения, то возвращается `FUZZY`. Если значение чёткости удовлетворяет пороговому значению, то проверяется реальность (`reality`). Если значение реальности выше порогового значения, считается, что это настоящее лицо, а если ниже — то это попытка атаки. В режиме распознавания видео рассчитывается среднее значение для всех кадров внутри видео, затем сравнивается со значением каждого кадра. Чем больше эти пороговые значения, тем строже требования к распознаванию.Установка глобального порогового значения для рамки:
```cpp
void SetBoxThresh(float box_thresh);
```
Значение по умолчанию — 0.8. Это пороговое значение для существования атакующего медиума; чем выше это значение, тем строже требования к атакующему медиуму. Обычно этот параметр не требует корректировки.
Для каждого из этих параметров существует соответствующий метод доступа (`Getter`), который можно получить, заменив префикс `Set` на `Get`.
### 4.3 Отладка параметров
При использовании программы часто возникают вопросы относительно пороговых значений. Для отладки пороговых значений распознавания здесь представлены функции для получения баллов каждого кадра.Ниже приведена функция для получения баллов после выполнения распознавания:
```cpp
void predict_log(seeta::FaceAntiSpoofing *fas, const SeetaImageData &image, const SeetaRect &face, const SeetaPointF *points) {
auto status = fas->Predict(image, face, points);
float clarity, reality;
fas->GetPreFrameScore(&clarity, &reality);
std::cout << "clarity = " << clarity << ", reality = " << reality << std::endl;
}
```
После вызова методов `Predict` или `PredictVideo`, можно использовать метод `GetPreFrameScore` для получения оценки распознавания последнего введенного кадра.
>
> См.: `seeta/FaceAntiSpoofing.h`
>
### 4.3 Дополнительные рекомендации
Содержание живого лица включает проверку четкости изображения, но всё ещё требуются требования к другим аспектам качества, такому как разрешение лица 128x128 и выше, равномерное освещение, естественные эмоции и т. д. Основным фактором, влияющим на точность распознавания, является окружающая среда освещения, что может быть достигнуто с помощью модуля "оценки качества" ниже.Кроме того, локальное распознавание живых лиц использует информацию контекста вокруг лица, поэтому требуется, чтобы лицо не было слишком близко к краям изображения. Находясь на краях изображения, информация контекста будет отсутствовать, что повлияет на точность. В таких случаях обычно рисуют область интереса (ROI) на экране при взаимодействии пользователя, и внутри этой области ROI распознавание происходит при подходящем расстоянии и разрешении лица.
## 5\. Отслеживание лица
Отслеживание лица также основано на базовых модулях лица, задачей которых является решение проблемы идентификации лиц до начала процесса распознавания, используя характеристики видео для определения, какие лица являются одним и тем же лицом в последовательности видеокадров.
Здесь представлен структурный тип данных для хранения информации о найденных лицах:
```cpp
struct SeetaTrackingFaceInfo {
SeetaRect pos;
float score;
int frame_no;
int PID;
int step;
};
struct SeetaTrackingFaceInfoArray {
struct SeetaTrackingFaceInfo *data;
int size;
};
```
По сравнению со структурой `SeetaFaceInfo` здесь добавлено поле `PID`. Поле `frame_no` и `step` предназначены для внутреннего отладочного использования и обычно не используются. Поле `pos` имеет тип `SeetaRect` и может использоваться вместо прямого результата детектора лица.`PID` представляет уникальный идентификатор человека. Для лиц, встречающихся в видео, если они были отслежены и им был присвоен одинаковый `PID`, то это указывает на то, что эти лица принадлежат одному и тому же человеку.Аналогично, перед использованием следует создать объект отслеживания лица:
```cpp
#include <seeta/FaceTracker.h>
seeta::FaceTracker* new_ft() {
seeta::ModelSetting setting;
setting.append("face_detector.csta");
return new seeta::FaceTracker(setting, 1920, 1080);
}
```
Этот код создает объект отслеживания лица для видео с разрешением `1920x1080`. Объект отслеживания лица принимает модель, которая используется для детектирования лиц.
Далее представлен пример функции для вывода `PID` и координат отслеживаемых лиц:
```cpp
#include <seeta/FaceTracker.h>
void track(seeta::FaceTracker* ft, const SeetaImageData& image) {
SeetaTrackingFaceInfoArray cfaces = ft->Track(image);
std::vector<SeetaTrackingFaceInfo> faces(cfaces.data, cfaces.data + cfaces.size);
for(auto& face : faces) {
SeetaRect rect = face.pos;
std::cout << "[" << rect.x << ", " << rect.y << ", "
<< rect.width << ", " << rect.height << "]: "
<< face.score << ", PID=" << face.PID << std::endl;
}
}
```
Когда логика детектирования прерывается или происходит смена видео, требуется очистка ранее отслеживаемых данных. В этом случае вызывается метод `Reset`, чтобы удалить все предыдущие результаты отслеживания и перезапустить счетчик `PID`:
```cpp
void reset(seeta::FaceTracker *ft) {
ft->Reset();
}
```
Как и в случае с детектором лиц, отслеживатель лиц также позволяет устанавливать базовые параметры детектора:
```cpp
ft->SetMinFaceSize(80); // Установка минимального размера лица 80
ft->SetThreshold(0.9f); // Установка порогового значения для оценки качества
```
Кроме того, есть специальные параметры, уникальные для отслеживания лиц.Установка стабильности видео:
```cpp
void seeta::FaceTracker::SetVideoStable(bool stable = true);
```
При установке этого параметра как `true`, производится плавное перемещение между кадрами, что делает результаты детекции более приятными для глаз.
Установка интервала:
```cpp
void seeta::FaceTracker::SetInterval(int interval);
```
По умолчанию значение интервала равно 10. Этот параметр используется для обнаружения новых `PID`. Детектор будет выполнять полное сканирование всего изображения для обнаружения новых `PID`. Если этот интервал слишком мал, это может замедлить процесс отслеживания (постоянное выполнение глобального сканирования). Если интервал слишком велик, новые лица могут не быть сразу же обнаружены.
Значение по умолчанию для `SetInterval` рассчитано при FPS равном 25. В реальных приложениях часто возникают пропущенные кадры. Если количество пропущенных кадров равно 1, то фактический FPS становится равным 12.5. В этом случае можно установить интервал отслеживания равным 5.
Отслеживание лиц не является тем же самым, что и алгоритм подсчета людей. Цель отслеживания лиц заключается в том, чтобы гарантировать, что один и тот же `PID` относится к одному и тому же человеку, что снижает нагрузку на систему распознавания. Кроме того, отслеживание лиц основывается на появлении лиц; если считать новыми людьми лица, которые не были полностью видимыми, это может существенно отличаться от реальности.Однако использование количества `PID` для подсчета общего числа лиц перед камерой вполне допустимо.
> Ссылка: `seeta/FaceTracker.h`
## 6. Оценка качества
Модуль оценки качества в `SeetaFace6` состоит из нескольких подмодулей, включая модули оценки яркости, четкости, целостности, четкости (глубинный анализ), позы, позы (глубинный анализ) и разрешения.
Сначала следует отметить общие интерфейсы всех модулей оценки качества.
```cpp
namespace seeta {
enum QualityLevel {
НИЗКИЙ = 0,
СРЕДНИЙ = 1,
ВЫСОКОЕ = 2,
};
}
```
Каждый подмодуль наследует базовый класс `QualityRule`, предоставляющий абстрактный результат оценки качества. Подклассы должны реализовать метод `check`, который принимает исходное изображение `image`, расположение лица `face` и массив ключевых точек `points`. Обратите внимание, что значение `N` обычно равно `5`.
Модуль оценки качества возвращает объект типа `QualityResult`, содержащий два поля — `level` и `score`. Поле `level` может иметь значения `LOW`, `MEDIUM`, `HIGH`, что соответственно указывает на низкое, среднее и высокое качество. Поле `score` связано с качеством положительно, но его диапазон значений не ограничен, то есть чем больше значение `score`, тем лучше качество. Поле `level` используется как основной показатель качества, а поле `score` применяется для более детального сравнения двух лиц.
Поэтому все модули используют метод `check` для выполнения своих задач.При описании каждого из этих модулей ниже акцент делается именно на характеристики конструктора.
Поскольку количество модулей велико, подробное описание интерфейсов можно найти в документации по конкретным версиям, доступных для скачивания.### 6.1 Оценка яркости
Оценка яркости представляет собой анализ равномерности яркости области лица. Если часть или вся область слишком светла или слишком темна, она будет отнесена к категории `LOW`.
**Объявление оценщика**, см. файл `seeta/QualityOfBrightness.h`
```cpp
class QualityOfBrightness : public QualityRule;
```
**Конструктор оценщика**
```cpp
QualityOfBrightness();
QualityOfBrightness(float v0, float v1, float v2, float v3);
```
Значения `{v0, v1, v2, v3}` имеют по умолчанию значения `{70, 100, 210, 230}`. Оценщик преобразует общую яркость из градации серого в уровень `level`, используя следующее отображение:
```
[0, v0), [v3, ~) => LOW
[v0, v1), [v2, v3) => MEDIUM
[v1, v2) => HIGH
```
### 6.2 Оценка четкости
Четкость здесь оценивается традиционным способом через потерю информации после двойного размытия изображения.
**Объявление оценщика**
См. файл `seeta/QualityOfClarity.h`
```cpp
class QualityOfClarity : public QualityRule;
```
### Конструктор оценщика
```cpp
QualityOfClarity();
QualityOfClarity(float low, float height);
```
Значения `{low, height}` имеют значения по умолчанию `{0.1, 0.2}`, где отношение отображается следующим образом:
```
[0, low) => НИЗКИЙ
[low, height) => СРЕДНИЙ
[height, ~) => ВЫСОКИЙ
```
### 6.3 Оценка целостности
Оценка целостности представляет собой простое суждение о том, является ли лицо неполным из-за того, что человек не полностью вошёл в поле зрения камеры. Этот метод не применим для оценки неполноты, вызванной заслонкой.Метод состоит в расширении области вокруг рамки детектированного лица. Если это расширение выходит за границы изображения, то считается, что данное изображение представляет собой неполное лицо, находящееся на краю изображения.#### Объявление оценщика
См. файл `seeta/QualityOfIntegrity.h`
```cpp
class QualityOfIntegrity : public QualityRule;
```
#### Конструктор оценщика
```cpp
QualityOfIntegrity();
QualityOfIntegrity(float low, float high);
```
Значения `{low, high}` имеют значения по умолчанию `{10, 1.5}`, единицы измерения — пикселей и пропорций соответственно. Отношение отображается следующим образом:
- Расширение лица на коэффициент `high` не вышло за пределы изображения => `ВЫСОКИЙ`
- Расширение лица на `low` пикселей не вышло за пределы изображения => `СРЕДНИЙ`
- Все остальные случаи => `НИЗКИЙ`
Значение `score`, которое имеет значение при уровне `СРЕДНИЙ`, указывает на долю выхода за пределы изображения.
### 6.4 Оценка положения
Оценщик положения здесь использует традиционный подход, который основывается на координатах 5 ключевых точек лица для определения, является ли положение лица передним.
#### Объявление оценщика
См. файл `seeta/QualityOfPose.h`
```cpp
class QualityOfPose : public QualityRule;
```
#### Конструктор оценщика
```cpp
QualityOfPose();
```
Этот конструктор не требует никаких параметров.
### 6.5 Оценка положения (глубинный)
Оценщик положения здесь использует глубинное обучение, которое основано на регрессии углов поворота головы в направлениях `yaw`, `pitch`, `roll` для оценки, является ли положение лица передним.
#### Объявление оценщика
См. файл `seeta/QualityOfPoseEx.h`
```cpp
class QualityOfPoseEx : public QualityRule;
```#### Конструктор оценщика
```cpp
QualityOfPoseEx(const SeetaModelSetting &setting);
```
При создании объекта `QualityOfPoseEx` требуется передать модель `pose_estimation.csta`. Примеры передачи моделей были подробно рассмотрены ранее, поэтому здесь повторяться не будем.
#### Настройка параметров
Учитывая большое количество параметров данного модуля, настройка параметров осуществляется через метод:
```cpp
void set(PROPERTY property, float value);
```
Поддерживаемые свойства включают:
```
YAW_LOW_THRESHOLD нижний порог для направления yaw
YAW_HIGH_THRESHOLD верхний порог для направления yaw
PITCH_LOW_THRESHOLD нижний порог для направления pitch
PITCH_HIGH_THRESHOLD верхний порог для направления pitch
ROLL_LOW_THRESHOLD нижний порог для направления roll
ROLL_HIGH_THRESHOLD верхний порог для направления roll
```
```cpp
auto qa = new seeta::QualityOfPoseEx(seeta::ModelSetting("pose_estimation.csta"));
qa->set(seeta::QualityOfPoseEx::YAW_LOW_THRESHOLD, 25);
qa->set(seeta::QualityOfPoseEx::YAW_HIGH_THRESHOLD, 10);
qa->set(seeta::QualityOfPoseEx::PITCH_LOW_THRESHOLD, 20);
qa->set(seeta::QualityOfPoseEx::PITCH_HIGH_THRESHOLD, 10);
qa->set(seeta::QualityOfPoseEx::ROLL_LOW_THRESHOLD, 33.33f);
qa->set(seeta::QualityOfPoseEx::ROLL_HIGH_THRESHOLD, 16.67f);
delete qa;
```
По установленным пороговым значениям, когда все три угла удовлетворяют максимальному пороговому значению, результат оценивается как `HIGH`. Когда один из углов имеет минимальное значение, он оценивается как `LOW`, а остальные случаи оцениваются как `MEDIUM`.
### 6.6 Оценка разрешенияЭто самая простая часть модуля оценки качества, которая проверяет разрешение области лица.
**Объявление оценщика**, см. файл `seeta/QualityOfResolution.h`
```cpp
class QualityOfResolution : public QualityRule;
```
**Конструктор оценщика**
```cpp
QualityOfResolution();
QualityOfResolution(float low, float height);
```
Значения `{low, high}` имеют по умолчанию `{80, 120}`, что соответствует следующим диапазонам:
```
[0, low) => НИЗКОЕ
[low, high) => СРЕДНЕЕ
[high, ~) => ВЫСОКОЕ
```
### 6.7 Оценка четкости (глубинная)
Модуль оценки качества (глубинной) по историческим причинам не наследует от `QualityRule`. В данном случае приведена версия, которая наследуется от `QualityRule` при использовании.
```cpp
#include "seeta/QualityStructure.h"
#include "seeta/QualityOfLBN.h"
namespace seeta {
class QualityOfClarityEx : public QualityRule {
public:
QualityOfClarityEx() {
m_lbn = std::make_shared<QualityOfLBN>(ModelSetting("quality_lbn.csta"));
m_marker = std::make_shared<FaceLandmarker>(ModelSetting("face_landmarker_pts68.csta"));
}
QualityOfClarityEx(float blur_thresh) {
m_lbn = std::make_shared<QualityOfLBN>(ModelSetting("quality_lbn.csta"));
m_marker = std::make_shared<FaceLandmarker>(ModelSetting("face_landmarker_pts68.csta"));
m_lbn->set(QualityOfLBN::PROPERTY_BLUR_THRESH, blur_thresh);
}
}
```
Метод `check` проверяет качество изображения с использованием модели `quality_lbn.csta`. В этом методе используется шаблон, который возвращает уровень качества изображения как высокий (`HIGH`) или низкий (`LOW`).
Обратите внимание, что этот код не предназначен для использования через открытый API и требует копирования в проект.### 6.8 Оценка зоны закрытого лица
При оценке зоны закрытого лица важно понимать, что алгоритм использует стратегию для выявления областей, закрытых масками. Ниже приведён пример кода, который можно использовать для оценки области закрытого лица:
```cpp
#include "seeta/QualityStructure.h"
#include "seeta/FaceLandmarker.h"
namespace seeta {
class QualityOfNoMask : public QualityRule {
public:
QualityOfNoMask() {
m_marker = std::make_shared<seeta::FaceLandmarker>(ModelSetting("face_landmarker_mask_pts5.csta"));
}
QualityResult check(const SeetaImageData &image, const SeetaRect &face, const SeetaPointF *points, int32_t N) override {
auto mask_points = m_marker->mark_v2(image, face);
int mask_count = 0;
for (auto point : mask_points) {
if (point.mask) mask_count++;
}
QualityResult result;
if (mask_count > 0) {
return {QualityLevel::LOW, 1 - float(mask_count) / mask_points.size()};
} else {
return {QualityLevel::HIGH, 1};
}
}
private:
std::shared_ptr<seeta::FaceLandmarker> m_marker;
};
}
```
Хотя здесь используется модуль `seeta::FaceLandmarker`, требуется использовать модель `face_landmarker_mask_pts5.csta`. Этот модуль предоставляет информацию о затенении для каждого обнаруженного ключевого точки.
### 6.9 Оценка качества
На этом этапе мы полностью представили все модули, которые могут использоваться для оценки качества. Поскольку каждый модуль упакован в соответствии с образом `QualityRule`, за исключением различий при конструировании и инициализации каждого модуля, каждое тестирование теперь можно выполнять одним способом.Сначала приведём пример использования `QualityRule` для оценки качества и вывода результата:
```cpp
void plot_quality(seeta::QualityRule *qr,
const SeetaImageData &image,
const SeetaRect &face,
const std::vector<SeetaPointF> &points) {
const char *level_string[] = {"LOW", "MEDIUM", "HIGH"};
seeta::QualityResult result = qr->check(image, face, points.data(), int(points.size()));
std::cout << "Уровень=" << level_string[int(result.level)] << ", балл=" << result.score << std::endl;
}
```
Для примера используем `QualityOfResolution`. После получения данных лица и ключевых точек, вызов для оценки разрешения выглядит следующим образом:
```cpp
seeta::QualityRule *qr = new seeta::QualityOfResolution();
plot_quality(qr, image, face, points);
delete qr;
```
Если вам нужно оценить другое качество, просто замените часть кода `QualityOfResolution`.
Конечно, этот кодовый блок не представляет собой окончательную реализацию. Объект `qr` может быть сохранён и восстановлен, поэтому нет необходимости временно создавать и освобождать его каждый раз.
В реальных приложениях часто требуется создание нескольких объектов `QualityRule`, где обычно требуется, чтобы все они вернули значение `HIGH`, прежде чем считать изображение качественным.
Кроме того, в зависимости от бизнес-требований, можно добавить другие модули для оценки качества.
По умолчанию пороговые значения всех модулей качества уже настроены и обычно не требуют изменения. Эти значения хорошо подходят для работы с другими модулями `SeetaFace`.
> Внимание: Все подмодули этого комплекта находятся в библиотеке `SeetaQualityAssessor300`.
> Ссылки: `seeta/QualityOfBrightness.h`, `seeta/QualityOfIntegrity.h`, `seeta/QualityOfPose.h`, `seeta/QualityOfResolution.h`, `seeta/QualityOfClarity.h`, `seeta/QualityOfLBN.h`, `seeta/QualityOfPoseEx.h`
## 7. Определение атрибутов лица
На данный момент доступны два атрибута: определение возраста и пола.
### 7.1 Определение возраста
Прежде всего рассмотрим функцию создания предиктора возраста:
```cpp
#include <seeta/AgePredictor.h>
seeta::AgePredictor *new_ap() {
```
```cpp
seeta::ModelSetting setting;
setting.append("age_predictor.csta");
return new seeta::AgePredictor(setting);
}
```
Вызов идентификатора для распознавания и вывода результатов распознавания представлен ниже:
```cpp
#include <seeta/AgePredictor.h>
void plot_age(seeta::AgePredictor* ap,
const SeetaImageData& image,
const std::vector<SeetaPointF>& points)
{
assert(points.size() == 5);
int age = 0;
ap->PredictAgeWithCrop(image, points.data(), age);
std::cout << "возраст=" << age << std::endl;
}
```
Обычно значение `age` имеет различные ошибки в разных возрастных группах. При использовании обычно требуется отображение цифрового возраста в нужный возрастной диапазон, такой как молодость, зрелость и старость.
Видео анализ часто показывает колебание возраста при каждом кадре из-за особенностей алгоритмов глубинного обучения — небольшие изменения входных данных могут привести к значительным колебаниям результатов.Если одиночное распознавание кадра не демонстрирует таких колебаний, то в видео анализе обычно используется трекинг лица до распознавания, поэтому для одного человека будет выдаваться один результат.Конечно, простое улучшение распознавания лиц также применимо к атрибутивному распознаванию, основные моменты следующие: 1. Отсеивание изображений с низким качеством с помощью оценки качества; 2. Увеличение точности путём многократного распознавания и объединения результатов.
### 7.2 Распознавание пола
Аналогично рассмотрим функцию создания идентификатора:
```cpp
#include <seeta/GenderPredictor.h>
seeta::GenderPredictor* new_gp()
{
seeta::ModelSetting setting;
setting.append("gender_predictor.csta");
return new seeta::GenderPredictor(setting);
}
```
Вызов идентификатора для распознавания и вывода результатов распознавания представлен ниже:
```cpp
#include <seeta/GenderPredictor.h>
void plot_gender(seeta::GenderPredictor* gp,
const SeetaImageData& image,
const std::vector<SeetaPointF>& points)
{
assert(points.size() == 5);
seeta::GenderPredictor::GENDER gender = 0;
gp->PredictGenderWithCrop(image, points.data(), gender);
std::cout << "пол="
<< (gender == seeta::GenderPredictor::FEMALE ? "женский" : "мужской")
<< std::endl;
}
```
Конечно, можно заключить, что распознавание пола также может использовать оценку качества для обеспечения высокого качества изображений, тем самым повышая точность распознавания.
## 8. Распознавание лиц под масками
### 8.1 Детекция масок
Аналогично рассмотрим функцию создания детектора:
```cpp
#include <seeta/MaskDetector.h>
seeta::MaskDetector* new_md()
{
seeta::ModelSetting setting;
setting.append("mask_detector.csta");
}
// Вызов функции для распознавания маски и вывода результатов приведен ниже:
``````cpp
#include <seeta/MaskDetector.h>
void plot_mask(seeta::MaskDetector* md,
const SeetaImageData& image,
const SeetaRect& face) {
float score = 0;
bool mask = md->detect(image, face, &score);
std::cout << std::boolalpha << "маска=" << mask
<< ", score=" << score
<< std::endl;
}
```
Общее правило состоит в том, что если значение `score` больше 0.5, то считается, что лицо носит маску.
### 8.2 Распознавание Лица с Маской
Распознавание лица с маской основывается на использовании модуля распознавания лица с маской. Вместо обычной модели используется специализированная модель для распознавания лиц с масками. Ниже представлен пример создания объекта для распознавания лиц с масками:
```cpp
#include <seeta/FaceRecognizer.h>
seeta::FaceRecognizer* new_mask_fr() {
seeta::ModelSetting setting;
setting.append("face_recognizer_mask.csta");
return new seeta::FaceRecognizer(setting);
}
```
Необходимо отметить, что распознавание лица с маской использует другой метод выделения области лица (`CropFaceV2`) по сравнению с обычным распознаванием лица.
Обычно система распознавания лица с маской работает следующим образом:

Как видно, распознавание лица с маской основано на использовании информации о не защищённой части лица.
Это позволяет сделать вывод, что:
- В общих условиях без использования маски, точность распознавания лиц с масками будет ниже, чем у стандартных моделей распознавания лиц.
- В условиях использования маски, специализированная модель распознавания лиц с масками будет работать лучше.
```Когда требования системы не слишком высокие, нет необходимости использовать обе модели одновременно. Модель распознавания лиц с масками может эффективно работать даже на лице без маски, используя информацию о незащищённой части лица. Когда большинство лиц в системе носят маски, можно использовать только специализированную модель распознавания лиц с масками.
Наконец, важно помнить, что характеристики, извлечённые различными моделями распознавания, не могут быть сравнимы друг с другом. Характеристики могут быть сравнимы только при условии, что они были извлечены одной и той же моделью.
## 9. Определение состояния глаз
Определение состояния глаз позволяет определять, открыты ли глаза, или есть какие-либо препятствия или проблемы с изображением, которые затрудняют определение.
Представленное здесь состояние глаз представляет собой тип перечисления `seeta::EyeStateDetector::EYE_STATE`, который содержит четыре значения: `EYE_CLOSE`, `EYE_OPEN`, `EYE_RANDOM`, `EYE_UNKNOWN`. Эти значения соответственно означают: закрытые глаза, открытые глаза, область, которая не является глазами, и неопределённое состояние.
Сначала создаётся детектор состояния глаз:```cpp
#include <seeta/EyeStateDetector.h>
```
```c++
seeta::EyeStateDetector* new_esd() {
seeta::ModelSetting setting;
setting.append("eye_state.csta");
return new seeta::EyeStateDetector(setting);
}
```
Приведён ниже функционал для выявления и вывода состояния глаз:
```cpp
#include <seeta/EyeStateDetector.h>
void eye_state(seeta::EyeStateDetector* esd,
const SeetaImageData& img,
const std::vector<SeetaPointF>& points) {
seeta::EyeStateDetector::EYE_STATE left_eye, right_eye;
const char* EYE_STATE_STR[] = {"закрыт", "открыт", "случайный", "неизвестен"};
esd->Detect(img, points.data(), left_eye, right_eye);
std::cout << "Глаза: (" << EYE_STATE_STR[left_eye] << ", "
<< EYE_STATE_STR[right_eye] << ")" << std::endl;
}
```Здесь состояние глаз определяется с помощью функции `Detect`, которая принимает в качестве входных данных исходное изображение и соответствующие координаты пяти точек. Это позволяет одновременно проверять состояние левого и правого глаз. Здесь "левый" и "правый" относятся к положению глаз на изображении.
## 10. Поддержка нескольких наборов команд
До этого момента мы рассмотрели основные модули и функции. В этом разделе мы обсудим вопросы, связанные с развертыванием.
Наши основные вычислительные библиотеки — это `libtennis.so` (на Windows — `tennis.dll`). Они требуют поддержки команд AVX и FMA. Чтобы обеспечить работу на процессорах, не поддерживающих эти команды, мы разработали стратегию динамической загрузки библиотек.
При сборке также включены следующие версии библиотек: `tennis_haswell`, `tennis_sandy_bridge`, `tennis_pentium`. Эти библиотеки предназначены для различных архитектур и поддерживают различные наборы команд:
| Библиотека | AVX | SSE | FMA |
|---------------------|-----|-----|-----|
| tennis_haswell | Да | Да | Да |
| tennis_sandy_bridge | Да | Да | Нет |
| tennis_pentium | Нет | Да | Нет |
При развертывании все эти библиотеки должны быть помещены в ту же директорию, что и библиотека `tennis`.Конечно, если известно, какие команды поддерживает конкретная платформа, можно использовать только одну библиотеку. Например, если известно, что поддерживается AVX и FMA, то можно переименовать `tennis_haswell` в `tennis` и установить только эту библиотеку при развертывании.## 11. Другие языки
SeetaFace6 поддерживает только основной интерфейс на C++. Разработчики могут расширить его до других языков программирования через встроенные средства этих языков.
Вот примеры расширения для разных языков:
«[Расширение Python для C/C++](https://docs.python.org/zh-cn/3.8/extending/extending.html)»
«[Основы JNI для Java](https://blog.csdn.net/yingshukun/article/details/79053061?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task)»
«[Документация Oracle Java](https://docs.oracle.com/en/java/)»
### Часто задаваемые вопросы
1. **Несоответствие версий?**
В проекте было решено выпустить его как общественную версию v3, но в процессе реализации была выбрана коммерческая версия v6. Несоответствие версий связано с управлением версиями коммерческого выпуска и независимым управлением версиями общественного доступа. Теперь версия унифицирована до v6. Однако в ходе проекта все еще используется термин "SeetaFace3". Не стоит беспокоиться — версии v6 и v3 являются одной и той же версией.
2. **Когда будет открыт этот выпуск?**
Предыдущие открытия были основаны на новых коммерческих версиях. На этот раз был выпущена коммерческая версия сразу, поэтому в ближайшее время нет планов по открытию этого выпуска. После коммерческого выпуска SeetaFace7 будет рассмотрен вопрос об открытии версии v6.3. **Может ли алгоритм обрабатывать серые изображения?**
Для серых изображений инструменты SeetaFace, за исключением модуля живости, могут работать корректно. Однако точность работы может снижаться. При использовании серых изображений в каждом модуле важно преобразовать изображение в формат BGR перед отправкой в алгоритм.
4. **Может ли алгоритм обрабатывать изображения ближнеинфракрасного диапазона?**
Версия SeetaFace6 предназначена для обработки цветных изображений видимого спектра. Поддержка изображений ближнеинфракрасного диапазона недоступна.
Важно отметить, что изображения ближнеинфракрасного диапазона не эквивалентны серым изображениям, хотя последние часто имеют черно-белый характер. Изображения серого уровня создаются при помощи волн видимого спектра, тогда как изображения ближнеинфракрасного диапазона создаются при помощи невидимых волн. Эти два типа изображений имеют существенные различия.
Серые изображения поддерживаются, а изображения ближнеинфракрасного диапазона не поддерживаются напрямую.
5. **Какова необходимая конфигурация для запуска?**
Хотя есть поддержка нескольких наборов команд, мы рекомендуем использовать процессоры с поддержкой ускоренной командной системы (например, AVX и FMA для X86, NEON для ARM).
Архитектура X86 поддерживает AVX и FMA, а также OpenMP. Архитектура ARM версии 8 и выше поддерживает NEON и OpenMP.Со всеми этими возможностями вы получите базовый уровень опыта, однако качество опыта и стоимость обычно пропорциональны друг другу. Не существует минимальной конфигурации, основанной только на аппаратном обеспечении; каждое решение будет адаптироваться к конкретному набору требований.6. **Can the model be trained?**
The code for training will be available but it must use the "Hailong" platform from VisionTrek company. This is due to current commercial agreements and the question is currently in the planning stage.
7. **Can version SeetaFace6 be used for free in commercial purposes?**
Yes, it can be used for free in commercial purposes.
8. **Does the system support compatible work on Windows XP?**
Yes, if the final executable file is compiled for compatibility with Windows XP. However, the system Windows XP does not support dynamic scheduling of commands.
## b. Applications
## c. Code Blocks
### 1. Wrapper for `seeta::ModelSetting`
```cpp
class ModelSetting : public SeetaModelSetting {
public:
using self = ModelSetting;
using supper = SeetaModelSetting;
enum Device {
AUTO,
CPU,
GPU
};
~ModelSetting() = default;
ModelSetting()
: supper({
SEETA_DEVICE_AUTO, 0, nullptr
}) {
this->update();
}
ModelSetting(const supper& other)
: supper({
other.device, other.id, nullptr
}) {
if (other.model) {
int i = 0;
while (other.model[i]) {
m_model_string.emplace_back(other.model[i]);
++i;
}
}
this->update();
}
ModelSetting(const self& other)
: supper({
other.device, other.id, nullptr
}) {
this->m_model_string = other.m_model_string;
this->update();
}
ModelSetting& operator=(const supper& other) {
this->operator=(self(other));
return *this;
}
ModelSetting& operator=(const self& other) {
this->device = other.device;
this->id = other.id;
this->m_model_string = other.m_model_string;
this->update();
return *this;
}
``````markdown
}
private:
std::vector<const char*> m_model;
std::vector<std::string> m_model_string;
/**
* \brief Обновление буфера модели
*/
void update() {
m_model.clear();
m_model.reserve(m_model_string.size() + 1);
for (auto &model_string : m_model_string) {
m_model.push_back(model_string.c_str());
}
m_model.push_back(nullptr);
this->model = m_model.data();
}
};
```
В данном случае нет необходимости в переводе, поскольку текст уже был переведён ранее. Сохранены также все специальные символы и структура документа.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )