Kithara和Dlib进行人脸实时检测
目录
- Kithara和Dlib进行人脸实时检测
- ResNet (残差网络)
- 流程介绍
- 核心代码
- 性能测试
- 开源源码
ResNet (残差网络)
ResNet,全称为Residual Network(残差网络),是由何凯明(Kaiming He)、张祥雨(Xiangyu Zhang)、任少卿(Shaoqing Ren)和孙剑(Jian Sun)在2015年提出的深度学习架构。这项工作在当年的计算机视觉领域顶级会议CVPR上获得了最佳论文奖,并且ResNet架构因其在ImageNet图像分类挑战中的卓越表现而迅速成为深度学习领域的里程碑之一。
ResNet不仅在图像分类任务中表现出色,还被广泛应用于目标检测、语义分割、人脸识别等多个计算机视觉领域,以及自然语言处理和其他机器学习任务中。
本次人像识别就是通过Dlib已经训练好的ResNet模型进行人脸识别。
流程介绍
Kithara RealTime Suite、OpenCV、Dlib与Qt结合使用ResNet进行实时人脸识别的流程可以概括如下:
-
Kithara 实时图像采集:
Kithara RealTime Suite 在操作系统内核层处理图像采集,确保低延迟和高精度的时间响应,这对于实时应用至关重要。
它直接从摄像头或图像传感器获取图像数据,由于其运行在内核级别,可以避免用户空间程序可能引入的额外延迟。 -
OpenCV 图像转换:
OpenCV 是一个强大的计算机视觉库,用于处理图像和视频流。
它将从Kithara接收到的原始图像数据转换成适合进一步处理的格式,例如转换色彩空间或调整尺寸。 -
OpenCV 到 Dlib 图像适配:
OpenCV 处理后的图像被转换成 Dlib 可以处理的格式。Dlib 是一个用于机器学习和数据分析的C++库,特别擅长处理计算机视觉任务。
使用Dlib的深度残差网络(ResNet)进行人脸识别,这包括人脸检测和特征提取。 -
共享内存传输:
处理好的图像数据通过共享内存机制从内核层传输到用户空间的应用层。共享内存是一种进程间通信方式,允许不同进程之间高效地交换大量数据,而无需复制数据。
这种机制保证了图像数据可以在不同层级之间快速传递,减少了数据传输的开销。 -
Qt 渲染与交互:
Qt 是一个跨平台的图形用户界面工具包,用于创建高性能的桌面和移动应用程序。
Qt 接收处理好的图像数据,并负责将其渲染在屏幕上,同时提供用户界面元素,如按钮、滑块等,以便用户与系统进行交互。
用户可以通过Qt界面查看识别结果,控制摄像头设置,或者执行其他操作。
整个流程从硬件层面的图像采集开始,经过多级软件处理,最终在用户界面上呈现结果,形成了一个完整的实时人脸识别系统。
核心代码
- 图像采集部分
// 这是实时任务将运行的函数,并对接收到的图像执行 Dlib 操作。只有实时任务才应调用 Dlib 函数。
KSError __stdcall Dlibcallback(void * /*pArgs*/, void * /*pContext*/)
{
// 在内核层模式下自动并行化 OpenCV 可能会与您的实时应用程序冲突。 建议关闭自动并行化,除非真的需要
// 禁用并行化
cv::setNumThreads(0);
// 表示已准备好处理图像。
krenel_data_ptr_->ready = 1;
DlibHandle dlib_handle;
// 图形抖动性测试
int64 last_diff_time {0};
int is_valid_time = 0; // 时间是否有效 0 无效 1 时间有效
int count = 0; // 计数器
int64 jitter_time_sum {0}; // 抖动总时间
// 处理循环,此循环仅在发出中止信号时停止。
for (;;)
{
// 等待图像接收或停止的通知。
KSError error = KS_waitForEvent(krenel_data_ptr_->image_received_event_handle, KSF_NO_FLAGS, 0);
if (error != KS_OK) { KS_printK("KS_waitForEvent failed! \n"); }
if (krenel_data_ptr_->abort != 0) { break; }
// 计数器
count++;
// 获取当前时间,用于计算图像处理的抖动时间
int64 last_time {0};
error = KS_getClock(&last_time, KS_CLOCK_MEASURE_HIGHEST);
if (error != KS_OK) { return error; }
// 获取接收到的图像数据的缓冲区
KSCameraBlock *camera_block;
void *image_data;
error = KS_recvCameraImage(krenel_data_ptr_->stream_handle, &image_data, &camera_block,KSF_NO_FLAGS);
if (error != KS_OK)
{
krenel_data_ptr_->ready = 1;
continue;
}
//如果接收到的块类型不是图像,我们跳过。在任何情况下,如果 KS_recvCameraImage() 成功接收到的缓冲区必须使用 KS_releaseCameraImage() 释放。
if (camera_block->blockType != KS_CAMERA_BLOCKTYPE_IMAGE)
{
KS_releaseCameraImage(krenel_data_ptr_->stream_handle, image_data, KSF_NO_FLAGS);
break;
}
// 在构建 OpenCV cv::Mat 之前,请检查接收到的图像是否具有正确的像素格式。
const auto *image_block = reinterpret_cast<KSCameraImage *>(camera_block);
// 图像转换
cv::Mat image = dlib_handle.CreateMat(image_block->height, image_block->width, image_block->pixelFormat, image_data, image_block->linePadding);
// 获取人脸名称
for (char & chr : krenel_data_ptr_->image_info.person_name)
{
chr = '\0';
}
std::string name{};
// 面部识别检测
image = dlib_handle.FaceDetect(image,name);
KS_printK("name:%s\n",name.c_str());
if (const size_t ret_size = KSRTL_strlen(name.c_str()); ret_size < NAME_SIZE - 1)
{
KSRTL_strncpy(krenel_data_ptr_->image_info.person_name, name.c_str(), NAME_SIZE);
}
else
{
KSRTL_strncpy(krenel_data_ptr_->image_info.person_name, nullptr, NAME_SIZE);
}
error = KS_releaseCameraImage(krenel_data_ptr_->stream_handle, image_data, KSF_NO_FLAGS);
if (error != KS_OK) { KS_printK("KS_releaseCameraImage failed! \n"); }
// 填充图像信息到共享内存中
krenel_data_ptr_->image_info.image_height = image.rows;
krenel_data_ptr_->image_info.image_width = image.cols;
krenel_data_ptr_->image_info.pixel_format = image_block->pixelFormat;
if (image_block->pixelFormat == KS_CAMERA_PIXEL_MONO_8)
{
KS_memCpy(pixel_buffer_, image.data, (int) image.cols * image.rows, KSF_NO_FLAGS);
}
else if (image_block->pixelFormat == KS_CAMERA_PIXEL_BGR_8)
{
KS_memCpy(pixel_buffer_, image.data, (int) image.cols * image.rows * 3, KSF_NO_FLAGS);
}
// 图形处理完成后,减去上次处理完成的时间
int64 time {0};
error = KS_getClock(&time, KS_CLOCK_MEASURE_HIGHEST);
if (error != KS_OK) { return error; }
// 检测圆处理时间
const int64 diff_time = time - last_time;
int64 time_cyc = diff_time;
KS_convertClock(&time_cyc, KS_CLOCK_MEASURE_HIGHEST, KS_CLOCK_MACHINE_TIME, KSF_NO_FLAGS);
krenel_data_ptr_->jitter_value.time_cyc = time_cyc;
if (is_valid_time == 0)
{
last_diff_time = diff_time;
is_valid_time = 1;
}
else
{
// 处理时间的抖动
const int64 jitter_time = diff_time - last_diff_time;
int64 single_time = jitter_time;
KS_convertClock(&single_time, KS_CLOCK_MEASURE_HIGHEST, KS_CLOCK_MACHINE_TIME, KSF_NO_FLAGS); // 100 ns 为单位
last_diff_time = diff_time;
jitter_time_sum += single_time;
if (krenel_data_ptr_->jitter_value.lat_min > single_time)
{
krenel_data_ptr_->jitter_value.lat_min = single_time;
}
if (krenel_data_ptr_->jitter_value.lat_max < single_time)
{
krenel_data_ptr_->jitter_value.lat_max = single_time;
}
krenel_data_ptr_->jitter_value.lat_avg = jitter_time_sum / count;
krenel_data_ptr_->jitter_value.cur_val = single_time;
}
krenel_data_ptr_->ready = 1;
// 启动相机拍摄获取下一帧图像 KS_CAMERA_SINGLE_FRAME 单帧获取
KS_startCameraAcquisition(krenel_data_ptr_->camera_handle, KS_CAMERA_SINGLE_FRAME, KSF_NO_FLAGS);
}
return KS_OK;
}
- 人脸检测部分
cv::Mat DlibHandle::FaceDetect(const cv::Mat &mat, std::string &name)
{
try
{
if (mat.empty())
{
return {};
}
constexpr int zoom = 2;
cv::Mat src = mat;
cv::Mat gray;
// 降低采样
cv::pyrDown(mat, gray, cv::Size(mat.cols / zoom, mat.rows / zoom));
// 转换为灰度图
if (mat.type() == CV_8UC3)
{
cv::cvtColor(gray, gray, cv::COLOR_BGR2GRAY);
}
// 将OpenCV图像转换成Dlib的matrix
dlib::array2d<dlib::bgr_pixel> dlib_img;
dlib::assign_image(dlib_img, dlib::cv_image<uchar>(gray));
// 提取人脸
const std::vector<dlib::rectangle> dets = detector_(dlib_img);
std::vector<dlib::matrix<dlib::rgb_pixel> > faces;
//std::vector<dlib::full_object_detection> shapes;
for (auto det: dets)
{
//画出人脸所在区域
cv::Rect r;
r.x = det.left() * zoom;
r.y = det.top() * zoom;
r.width = det.width() * zoom;
r.height = det.height() * zoom;
cv::rectangle(src, r, cv::Scalar(0, 255, 0), 3, cv::LINE_8, 0);
// 提取面部特征
auto shape = predictor_(dlib_img, det);
//shapes.push_back(shape);
// 根据面部轮廓裁剪面部图像
dlib::matrix<dlib::rgb_pixel> face_chip;
// 人脸对齐
extract_image_chip(dlib_img, get_face_chip_details(shape, 150, 0.25), face_chip);
faces.push_back(std::move(face_chip));
}
// 绘制特征点
//DrawFaceFeaturePoints(src, shapes);
// 人像追踪
bool is_check {false};
if (last_rectangles_.size() == dets.size())
{
for (int i = 0; i < dets.size(); ++i)
{
int x = dets[i].left() - last_rectangles_[i].left() > 0
? dets[i].left() - last_rectangles_[i].left()
: last_rectangles_[i].left() - dets[i].left();
int y = dets[i].top() - last_rectangles_[i].top() > 0
? dets[i].top() - last_rectangles_[i].top()
: last_rectangles_[i].top() - dets[i].top();
if (x < 100 && y < 100)
{
cv::Point origin;
origin.x = dets[i].left() * zoom;
origin.y = dets[i].top() * zoom - 15;
cv::putText(src, last_name_[i], origin, cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 0, 0), 2, 2, false);
}
else
{
is_check = true;
}
}
}
else
{
is_check = true;
}
if (is_check)
{
last_name_.clear();
// 提取这一组RseNet人脸识别数据
const std::vector<dlib::matrix<float, 0, 1> > face_descriptors = res_net_face_recognition_(faces);
for (int i = 0; i < face_descriptors.size(); ++i)
{
for (const auto &[key,value]: name_face_map_)
{
// 计算欧几里得距离,获取相似度
vec_error_ = dlib::length(face_descriptors[i] - value);
// 人像已被识别
if (vec_error_ < error_min_)
{
// 识别成功后,去除_后面的数字编号
name = key.substr(0, key.find("_"));
break;
}
else
{
if (unknow_check_count_ >10)
{
name = "UnKnown";
unknow_check_count_ = 0;
}
unknow_check_count_++;
last_rectangles_.clear();
}
}
//将文本框居中绘制
cv::Point origin;
origin.x = dets[i].left() * zoom;
origin.y = dets[i].top() * zoom - 15;
cv::putText(src, name, origin, cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 0, 0), 2, 2, false);
last_name_[i] = name + " " + std::to_string(vec_error_);
}
}
last_rectangles_ = dets;
return src;
}
catch (...)
{
return {};
}
}
软件效果图:
性能测试
Kithara Windows实时套件通过其独特的CPU核心独占特性,能够在Windows环境下即便CPU处于高负载状态时,依然保持检测任务的稳定执行和结果的及时输出。然而,考虑到人像识别算法对计算资源(尤其是CPU和GPU)的高需求,实际性能在不同硬件配置的机器上会呈现出显著差异。
实际测试表明,人像识别的效率和可靠性高度依赖于图像处理的优化水平。越是精细的程序优化,比如针对特定硬件的代码调优、算法的高效实现以及内存管理的优化,都能显著减少图像处理所需时间,同时提升系统的整体稳定性。
总的来说,Kithara的实时处理能力确保了在复杂环境下的可靠运行,但为了最大化人像识别的性能,深入的图像解析优化和适应不同硬件的定制化调整是必不可少的。这种优化不仅能加快处理速度,还能增强系统的鲁棒性和一致性。
开源源码
测试源码现已开源,项目Demo均是测试代码,请勿用于实际项目!