OpenVINO是Intel推出的计算机视觉深度学习推理加速库,支持Intel CPU, 核心显卡,VPU和FPGA。前三篇文章分别介绍了OpenVINO的环境搭建、重要文件、目录、环境变量, 以及人脸特征提取SDK的开发。
人脸识别的完整流程主要包括人脸检测(人脸位置定位),人脸特征提取和人脸特征检索三个阶段。只提供人脸特征提取并不能完成完整的人脸识别。人脸识别的第一步是从图片中找出人脸的位置,然后将人脸图片抠出来作为人脸特征提取的输入,从而人脸特征提取器提出人脸特征,用于后续的人脸特征检索,因此本文就来实战一下人脸检测。本文所有操作的系统环境为Ubuntu16.04。
人脸检测的C++工程已经推到了Github: adamydwang/face-openvino。 该工程取名为face-openvino是希望能够包括整个人脸识别的完整sdk, 因此该工程集成了Github: adamydwang/insightface-openvino。
CMake工程目录结构
本文延续insightface-openvino工程的做法,依然采用CMakeLists来构建C++工程,目录如下:
- bin: 用于保存编译生成的人脸特征提取和人脸检测demo可执行程序。
- build: 用于保存编译过程中产生的临时文件。
- demo: 存放人脸特征提取和人脸检测demo程序。
- image: 存放了一张测试图片。
- include: 用于保存C++头文件。
- lib: 用于保存编译生成的人脸相关的sdk,包括人脸特征提取和人脸检测。
- model: 用于保存openvino模型文件。
- src: 用于保存C++源文件。
完整的CMakeLists.txt文件内容如下
本工程的CMakeLists几乎是最简版,只包括了必须的内容,可用于初学者参考学习CMakeLists的编写。
cmake_minimum_required(VERSION 2.7)
project(insightface)
add_definitions(-std=c++11)
find_package(InferenceEngine REQUIRED)
find_package(OpenCV REQUIRED)
include_directories(${PROJECT_SOURCE_DIR}/include
${InferenceEngine_INCLUDE_DIRS}
${OpenCV_INCLUDE_DIRS})
set(LIBS ${InferenceEngine_LIBRARIES} ${OpenCV_LIBRARIES})
set(SRCS ${PROJECT_SOURCE_DIR}/src/insightface.cpp
${PROJECT_SOURCE_DIR}/src/facedetector.cpp
${PROJECT_SOURCE_DIR}/src/baseface.cpp)
add_library(face STATIC ${SRCS})
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_executable(feature_extract ${PROJECT_SOURCE_DIR}/demo/feature_extract.cpp)
target_link_libraries(feature_extract face ${LIBS})
add_executable(face_detect ${PROJECT_SOURCE_DIR}/demo/face_detect.cpp)
target_link_libraries(face_detect face ${LIBS})
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
BaseFace类实现
本工程是在insightface-openvino工程的基础上开发的。当撰写FaceDetector类的时候发现, FaceDetector和InsightFace的方法90%的操作是相同的,因此果断从insightface-openvino中提取出BaseFace类,人脸检测和人脸特征提取都会继承自该类。
因为BaseFace类是从insightface-openvino中提出来的,因此BaseFace与上一篇文章中InsightFace类的实现重合度极高。
BaseFace类定义:
class BaseFace {
public:
//model: openvino 模型xml文件全路径
//dev: 设备类型,包括CPU, GPU, VPU等
//width, height: 模型接受的输入图片宽和高
BaseFace(const std::string& model, const std::string& dev, int width=112, int height=112);
~BaseFace();
int init();
int process(cv::Mat& image, std::vector<float>& res);
void preprocess(cv::Mat& image);
InferenceEngine::InferRequest m_ireq;
std::string m_model;
std::string m_device;
std::string m_input_name;
std::string m_output_name;
int m_width;
int m_height;
};
BaseFace类重要方法实现(有删减,完整版请参考Github: adamydwang/face-openvino):
//初始化方法,BaseFace类实例话必须被执行一次
int BaseFace::init() {
try {
InferenceEngine::Core ie;
//1. 读取网络模型到Host内存
InferenceEngine::CNNNetwork network = ie.ReadNetwork(m_model);
//2. 设置输入参数
InferenceEngine::InputsDataMap input_info(network.getInputsInfo());
auto input = *input_info.begin();
m_input_name = input.first;
input.second->setPrecision(InferenceEngine::Precision::U8);
input.second->setLayout(InferenceEngine::Layout::NCHW);
//3. 将模型载入目标设备
InferenceEngine::ExecutableNetwork executable_network = ie.LoadNetwork(network, m_device);
m_ireq = executable_network.CreateInferRequest();
//4. 设置输出参数
InferenceEngine::OutputsDataMap output_info(network.getOutputsInfo());
m_output_name = output_info.begin()->first;
output_info.begin()->second->setPrecision(InferenceEngine::Precision::FP32);
catch(...) { }
return 0;
}
//CNN推理, 输入Mat格式BGR图片,输出模型原始推理结果
int InsightFace::process(cv::Mat& image, std::vector<float>& feature) {
//1.对输入图片预处理
preprocess(image);
//2.输入从Host拷贝至Device, 推理, 输出从Device拷贝至Host
m_ireq.Infer();
//3.从Host获取推理结果
InferenceEngine::Blob::Ptr output_blob = m_ireq.GetBlob(m_output_name);
const int dims = output_blob->size();
feature.resize(dims);
memcpy(feature.data(), output_blob->buffer(), dims * sizeof(float));
return 0;
}
//输入图片预处理
void InsightFace::preprocess(cv::Mat& image) {
cv::Mat resized;
//图片必须resize至模型所需要的大小
cv::resize(image, resized, cv::Size(m_width, m_height));
//将输入图片喂到可执行网络在Host对应的内存
InferenceEngine::Blob::Ptr blob = m_ireq.GetBlob(m_input_name);
unsigned char* ptr = (unsigned char*)blob->buffer();
for (int c = 0; c < 3; ++c) {
for (int y = 0; y < m_height; ++y) {
for (int x = 0; x < m_width; ++x) {
*(ptr++) = resized.at<cv::Vec3b>(y, x)[c];
}
}
}
}
FaceDetector类实现
FaceDetector类继承自BaseFace类
FaceDetector类定义:
class FaceDetector : public BaseFace {
public:
//model: 人脸检测openvino模型xml文件全路径
//dev: 用于OpenVINO推理的设备类型,包括CPU, GPU, VPU等
//width, height: 人脸检测模型输入图片的宽和高
//threshold: 人脸框置信度阈值,只有推理出的人脸框的得分高于该阈值,才输出对应的人脸框位置
FaceDetector(const std::string& model, const std::string& dev, int width=300, int height=300, float threhold=0.8);
~FaceDetector();
int init();
int process(cv::Mat& image, std::vector<cv::Rect>& faces);
private:
float m_threshold;
};
FaceDetector类实现:
FaceDetector::FaceDetector(const std::string& model, const std::string& dev, int width, int height, float threshold)
: BaseFace(model, dev, width, height), m_threshold(threshold) {
}
FaceDetector::~FaceDetector() {}
int FaceDetector::init() {
return BaseFace::init();
}
int FaceDetector::process(cv::Mat& image, std::vector<cv::Rect>& faces) {
//保存图片原始的尺寸,用于后续还原人脸框大小
int width = image.cols;
int height = image.rows;
BaseFace::preprocess(image);
std::vector<float> raws;
if (BaseFace::process(image, raws) < 0) {
return -1;
}
//output shape: [1,1,N,7]
//[image_id, predicted_label, confidence, x_min, y_min, x_max, y_max]
//refer to https://docs.openvinotoolkit.org/2019_R2/_intel_models_face_detection_retail_0005_description_face_detection_retail_0005.html
if (raws.size() % 7) {
return -2;
}
for (int i = 0; i < raws.size(); i += 7) {
//得分未超过阈值则跳过
if (raws[i+2] < m_threshold) {
continue;
}
int x = raws[i+3] * width;
int y = raws[i+4] * height;
int w = (raws[i+5] - raws[i+3]) * width;
int h = (raws[i+6] - raws[i+4]) * height;
faces.emplace_back(x, y, w, h);
}
return 0;
}
人脸检测Demo
demo程序非常简单,就是调用人脸检测sdk, 对本地图片进行人脸检测,并将人脸框画在图片上保存。
int main(int argc, char** argv) {
if (argc != 4) {
std::cout << "Usage: ./" << argv[0] << " model_path device image" << std::endl;
return -1;
}
std::string model_path = argv[1];
std::string device = argv[2];
std::string image_name = argv[3];
FaceDetector face(model_path, device);
if (face.init() < 0) {
std::cout << "FaceDetector init fail" << std::endl;
return -2;
}
cv::Mat image = cv::imread(image_name);
if (image.empty()) {
std::cout << "Invalid image" << std::endl;
return -3;
}
std::vector<cv::Rect> faces;
struct timeval start, end;
gettimeofday(&start, NULL);
face.process(image, faces);
gettimeofday(&end, NULL);
for (auto& rect : faces) {
std::cout << rect.x << ", " << rect.y << "," << rect.width << "," << rect.height << std::endl;
//画出人脸检测框
cv::rectangle(image, rect, cv::Scalar(255,0,0));
}
//保存带人脸框的图片
cv::imwrite("output.jpg", image);
std::cout << "Elapsed Time: " << (end.tv_sec - start.tv_sec)*1000 + (end.tv_usec - start.tv_usec) / 1000 << " ms" << std::endl;
return 0;
}
人脸检测结果:
本文暂时没有评论,来添加一个吧(●'◡'●)