一、引言
1.1 <libctk>的由来
1.2 <libctk>的设计理论依据
1.3 <libctk>的设计理念
二、<libctk>的依赖库
三、<libctk>的目录说明
四、<libctk>的功能模块及使用实例说明
4.1 日志模块
4.2 mysql client模块
4.3 ftp client模块
4.4 cv人脸检测与识别模块
五、下一步计划
5.1 后续版本规划
5.2 编写SDK
5.3 开源计划
一、引言
1.1 <libctk>的由来
设计初衷是作为<Smart-park-FaceDR-SVC>项目的辅助项目,个中缘由可查阅:智慧园区项目人脸检测与识别子项目之-总体设计
实际上,<libctk>发展到现在,已经完全独立于<Smart-park-FaceDR-SVC>项目,已经完完全全是linux下的一个shared library。
https://mp.csdn.net/mp_blog/creation/editor/139818825
1.2 <libctk>的设计理论依据:
笔者为了为本文作铺垫,专门在之前编写了:linux下的动态链接库的编码实现。
本文就不再描述<shared library>理论上的说明,一句话"干就完事!"(码农最喜欢的就是实操)。
1.3 <libctk>的设计理念
(1) 不重复造轮子
利用现有的开源库来避免重复造轮子,提高开发效率和代码质量。同时,通过学习这些开源库的源代码,提升自己的编程技能和代码水平。
(2) 优雅、简洁、易用
优雅:共享库的设计应该优雅简洁,遵循UNIX哲学,即"简单就是美"。避免过分复杂的设计,保持功能的简洁性和易懂性。
简洁:共享库应该提供清晰简洁的接口,尽量少暴露内部实现细节,使用户可以方便地调用库的功能,而不需要了解其具体实现。
易用:共享库应该易于安装、配置和使用,提供简单的API和文档,使用户能够快速上手并应用库的功能。
总的来说,Linux下共享库的设计应该遵循优雅、简洁、易用的原则,以提高代码的可维护性和可扩展性,同时提供良好的用户体验,让用户能够方便地使用和扩展库的功能。
二、<libctk>的依赖库
库名称 | 说明 |
---|---|
libconfig++ | libconfig++是一个C++版本的libconfig配置文件解析库。在Linux系统上,可以使用libconfig++库来方便地读取和修改配置文件,从而实现程序的灵活配置和参数设置。 |
spdlog | spdlog是一个快速、可扩展的C++日志记录库,提供了多种日志记录方式和格式化选项。它支持多线程并发日志记录,可以轻松地集成到各种应用程序中。 |
MySQL Connector/C++ | MySQL官方提供的C++库,用于在Linux系统上连接和操作MySQL数据库。需要在官网上下载对应OS版本的安装包。 |
libcurl-dev | libcurl-dev 是用于 Linux 系统的 libcurl 的开发包,用于编译和链接程序需要使用 libcurl 库的程序。通过安装 libcurl-dev,开发人员可以在其代码中使用 libcurl 的功能,如 HTTP 请求、FTP 传输等等。 |
opencv4.9 | 是一个开源的计算机视觉库,提供了很多计算机视觉和图像处理的功能,如图像处理、目标识别、特征检测等。OpenCV库可以在Linux下使用,并且提供了很多用于C++、Python和Java等编程语言的接口。 |
特别说明:以上库,笔者全部选择下载源码编译安装(从版本、个性化方面考虑)。当然,你也可选择apt-get方式安装。至于编译安装方法,你可以自行摆渡,也可以在笔者的博文中查找相关文章。
三、<libctk>的目录及CMakeList.txt文件说明
<libctk>的clion-IDE界面:
大概的目录结构是这样的:
libctk
│-- cmake
├-- conf
│-- include
|-----ctk
|-------cvFace.h
|-------cvUtils.h
|-------error.h
|-- onnx
├── src
│ ├── cvFace.cpp
│ ├── cvUtils.cpp
│ ├── error.cpp
│ └── ...
│-- build
└── CMakeList.txt
目录说明:
目录 | 说明 |
---|---|
cmake | .cmake文件的存放路径。如:FindOpencv.cmake、utils.cmake |
conf | 配置文件的存放路径。如:log.conf,mysql.conf,faceDR.conf |
include/ctk | 头文件的存放路径。 |
onnx | 存放<物体检测>的训练模型文件(ONNX文件) |
src | c++源代码文件的存放路径。 |
CMakeList.txt | 不用说明吧。 |
CMakeList.txt:
# ----------------------------------------------------------------------------
# Root CMake file for libctk
#
# From the off-tree build directory, invoke:
# $ cmake <PATH_TO_CTK_ROOT>
# Author: RemonLin
# ----------------------------------------------------------------------------
# Disable in-source builds to prevent source tree corruption.
if(" ${CMAKE_SOURCE_DIR}" STREQUAL " ${CMAKE_BINARY_DIR}")
message(FATAL_ERROR "
FATAL: In-source builds are not allowed.
You should create a separate directory for build files.
")
endif()
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
# message(STATUS "cmake_module_path is: ${CMAKE_MODULE_PATH}")
include(DepVersions)
#cmake_minimum_required(VERSION "${MIN_VER_CMAKE}" FATAL_ERROR)
cmake_minimum_required(VERSION 3.21..3.29 FATAL_ERROR)
# ---------------------------------------------------------------------------------------
# Start ctk project
# ---------------------------------------------------------------------------------------
include(cmake/utils.cmake)
ctk_extract_version()
message(STATUS "Build libctk: ${CTK_VERSION}")
project(ctk VERSION ${CTK_VERSION} LANGUAGES CXX)
# ---------------------------------------------------------------------------------------
# Dependencies
# ---------------------------------------------------------------------------------------
# Find libconfig++
# find_package(libconfig ${LIBCONFIG_VERSION} REQUIRED HINTS ${LIBCONFIG_INSTALLATION_PATH})
# Find spdlog 1.13.0
include(Findspdlog)
find_package(spdlog ${SPDLOG_VERSION} REQUIRED HINTS ${SPDLOG_INSTALLATION_PATH})
# Find OpenCV
include(FindOpenCV)
find_package(OpenCV ${OPENCV_VERSION} REQUIRED HINTS ${OPENCV_INSTALLATION_PATH})
# Find CURL
include(FindCURL)
find_package(CURL REQUIRED)
# must go before the project()/enable_language() commands
ctk_update(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configs" FORCE)
if(NOT DEFINED CMAKE_BUILD_TYPE)
message(STATUS "'Release' build type is used by default. Use CMAKE_BUILD_TYPE to specify build type (Release or Debug)")
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build")
endif()
if(DEFINED CMAKE_BUILD_TYPE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${CMAKE_CONFIGURATION_TYPES}")
endif()
include(CheckIncludeFile)
include(GNUInstallDirs)
# set CMAKE_INSTALL_PREFIX
get_filename_component(CTK_INSTALL_PREFIX ${CMAKE_SOURCE_DIR} DIRECTORY)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) # https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT.html
if(NOT CMAKE_TOOLCHAIN_FILE)
if(WIN32)
set(CMAKE_INSTALL_PREFIX "${CTK_INSTALL_PREFIX}/install" CACHE PATH "Installation Directory" FORCE)
else()
set(CMAKE_INSTALL_PREFIX "${CTK_INSTALL_PREFIX}/install" CACHE PATH "Installation Directory" FORCE)
endif()
else()
# any cross-compiling
set(CMAKE_INSTALL_PREFIX "${CTK_INSTALL_PREFIX}/install" CACHE PATH "Installation Directory" FORCE)
endif()
endif()
# set(CMAKE_INSTALL_PREFIX "${CTK_INSTALL_PREFIX}/install" CACHE PATH "Installation Directory" FORCE)
message(STATUS "install to:${CMAKE_INSTALL_PREFIX}")
# ---------------------------------------------------------------------------------------
# Compiler config
# ---------------------------------------------------------------------------------------
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# make sure __cplusplus is defined when using msvc and enable parallel build
if(MSVC)
string(APPEND CMAKE_CXX_FLAGS " /Zc:__cplusplus /MP")
endif()
set(CMAKE_CXX_EXTENSIONS OFF)
if(CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS" OR CMAKE_SYSTEM_NAME MATCHES "MINGW")
set(CMAKE_CXX_EXTENSIONS ON)
endif()
# ---------------------------------------------------------------------------------------
# compile option set
# ---------------------------------------------------------------------------------------
option(CTK_BUILD_ALL "Build all artifacts" OFF)
# build shared option
option(CTK_BUILD_SHARED "Build shared library" ON)
option(BUILD_SHARED_LIBS "Global flag to cause add_library() to create shared libraries if on." ${CTK_BUILD_SHARED})
# precompiled headers option
option(CTK_ENABLE_PCH "Build static or shared library using precompiled header to speed up compilation time" OFF)
# build position independent code
option(CTK_BUILD_PIC "Build position independent code (-fPIC)" ${CTK_BUILD_SHARED})
# example options
option(CTK_BUILD_EXAMPLE "Build example" ON)
# testing options
option(CTK_BUILD_TESTS "Build tests" OFF)
# warning options
option(CTK_BUILD_WARNINGS "Enable compiler warnings" OFF)
# install options
option(CTK_SYSTEM_INCLUDES "Include as system headers (skip for clang-tidy)." OFF)
option(CTK_INSTALL "Generate the install target" ON)
# option(CTK_NO_EXCEPTIONS "Compile with -fno-exceptions. Call abort() on any CTK exceptions" OFF)
if(CTK_BUILD_PIC)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
# ---------------------------------------------------------------------------------------
# Shared/static library
# ---------------------------------------------------------------------------------------
aux_source_directory(src/ CTK_SRCS)
if(CTK_BUILD_SHARED OR BUILD_SHARED_LIBS)
if(WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY)
list(APPEND CTK_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
endif()
add_library(ctk SHARED ${CTK_SRCS}
include/ctk/error.h
src/error.cpp
src/error.cpp)
# target_compile_definitions(CTK PUBLIC CTK_SHARED_LIB)
if(MSVC)
target_compile_options(ctk PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251
/wd4275>)
endif()
else()
add_library(ctk STATIC ${CTK_SRCS})
endif()
set(CTK_INCLUDES_LEVEL "")
if(CTK_SYSTEM_INCLUDES)
set(CTK_INCLUDES_LEVEL "SYSTEM")
endif()
set(LIBS_INCLUDE_DIR "/usr/local/include")
target_include_directories(ctk ${CTK_INCLUDES_LEVEL} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
"$<BUILD_INTERFACE:${LIBS_INCLUDE_DIR}"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
target_link_libraries(ctk PUBLIC
libconfig++.so; libspdlog.so; mysqlcppconn8; mysqlcppconn; ${CURL_LIBS}; ${OpenCV_LIBS}
)
set_target_properties(ctk PROPERTIES VERSION ${CTK_VERSION} SOVERSION
${CTK_VERSION_MAJOR}.${CTK_VERSION_MINOR})
set_target_properties(ctk PROPERTIES DEBUG_POSTFIX d)
# ---------------------------------------------------------------------------------------
# Build binaries
# ---------------------------------------------------------------------------------------
if(CTK_BUILD_EXAMPLE OR CTK_BUILD_ALL)
message(STATUS "Generating example(s)")
add_subdirectory(example)
# ctk_enable_warnings(example)
endif()
if(CTK_BUILD_TESTS OR CTK_BUILD_ALL)
message(STATUS "Generating tests")
enable_testing()
#add_subdirectory(tests)
endif()
# ---------------------------------------------------------------------------------------
# Install
# ---------------------------------------------------------------------------------------
message(STATUS "Generating install")
# ---------------------------------------------------------------------------------------
# Include files
# ---------------------------------------------------------------------------------------
message(STATUS "ctk bin dir is:${CMAKE_INSTALL_BINDIR}")
install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(
TARGETS ctk example
EXPORT ctk
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
INSTALL(DIRECTORY conf/ DESTINATION conf)
INSTALL(DIRECTORY onnx/ DESTINATION onnx)
四、<libctk>的功能模块及使用实例说明
4.1 日志模块
调用处必须:#include "ctk/xlogger.h"
void xlogger_sample() {
auto pLogger = ctk::log::createLogger(); // 全局调用一次即可,以后用spdlog::XXX(msg);
pLogger->error("libctk1.1.0 logger sample is Runing!!!"); //在调用处可以使用指针
spdlog::info("FaceDR service start!");
spdlog::error("出错了!");
spdlog::debug("出错了!");
}
logger的配置在../conf/log.conf文件
log_format = "[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [thread %t] %v";
log_name = "../logs/example.log"; /* 日志文件名称含路径 */
log_async = true;
log_type = "rotating"; /* daily || rotating*/
log_level = "info"; /* 日志等级info */
flush_level = "error"; /* 日志刷新等级*/
log_q_size = 8192; /* 异步日志有用. 线程池可容纳的日志记录数*/
log_thread_count = 1; /* 异步日志有用. 后台有多少个处理日志的子线程*/
log_flush_every = 5; /* 异步日志有用. 每5s刷新一次,将缓存的日志写入目标*/
daily_logger = {
log_hour = 0; /* 日志创建-时*/
log_minute = 0; /* 日志创建-分*/
};
rotating_logger = {
log_size = 3145728; /* 日志文件大小 */
max_files = 3; /* 最多保存日志文件数量*/
};
4.2 mysql client模块
两种连接mysql server方式:
client方式:具有pool功能。
// 传入参数方式调用
void mysqlx_client_sample() {
auto pClient1 = ctk::mysql::MyClient::create("192.168.1.207", 33060, "admin", "Admin207", 7);
auto pClient2 = ctk::mysql::MyClient::create(); // 从mysql.conf读取参数
//auto pLogger = ctk::log::createLogger();
auto pSession = std::make_shared<mysqlx::Session>(pClient1->getSession());
std::string plateNo;
mysqlx::Schema db = pSession->getSchema("tvdat");
mysqlx::Table table = db.getTable("vehicle_info");
spdlog::info("mysqlx_client_sample runing!");
for (size_t i =1; i <= 50; i++) {
plateNo = "闽K" + generate_random_string(5);
//spdlog::info(plateNo.c_str());
table.insert().values(plateNo, "Remon Lin").execute();
//table.select()
}
}
//!从 ../conf/mysql.conf读取参数
void mysqlx_client_sample_cfg() {
auto pClient = ctk::mysql::MyClient::create();
//auto pLogger = ctk::log::createLogger();
auto pSession = std::make_shared<mysqlx::Session>(pClient->getSession());
std::string plateNo;
mysqlx::Schema db = pSession->getSchema("park");
mysqlx::Table table = db.getTable("host");
spdlog::info("mysqlx_client_sample runing!");
std::string hostID("1217");
std::string ip("1217");
std::string txt("217 Linux server");
try {
table.insert().values(hostID, ip, txt).execute();
} catch (const mysqlx::Error& e) {
throw ctk::CtkException("mysql", e.what());
}
}
session方式:通过创建的session操作数据库。
// 传入参数方式调用
void mysqlx_session_sample() {
cout << "mysqlx_session_sample is runing!" << endl;
auto pSession = ctk::mysql::MySession::create("192.168.1.142", 33060, "admin", "123456");
std::string plateNo;
mysqlx::Schema db = pSession->getSchema("tvdat");
mysqlx::Table table = db.getTable("vehicle_info");
spdlog::info("mysqlx_session_sample runing!");
for (size_t i =1; i <= 60; i++) {
plateNo = "闽F" + generate_random_string(5);
//spdlog::info(plateNo.c_str());
table.insert().values(plateNo, "Remon Lin").execute();
}
}
//!从 ../conf/mysql.conf读取参数
void mysqlx_session_sample_cfg() {
cout << "mysqlx_session_sample_cfg is runing!" << endl;
auto pSession = ctk::mysql::MySession::create();
std::string plateNo;
mysqlx::Schema db = pSession->getSchema("tvdat");
mysqlx::Table table = db.getTable("vehicle_info");
spdlog::info("mysqlx_session_sample runing!");
for (size_t i =1; i <= 60; i++) {
plateNo = "京P" + generate_random_string(5);
//spdlog::info(plateNo.c_str());
table.insert().values(plateNo, "Remon Lin").execute();
}
}
mysql client的配置在../conf/mysql.conf文件
#----------------------------------------------------------------------------------------------------------------------
#maxSize:定义最大可能池化会话数的整数。如果在使用了最大会话数的情况下使用尝试从池中获取会话,它将等待可用会话,直到queueTimeout。默认值为25。
#queueTimeout:一个整数值,定义客户端等待获得可用会话的时间(以毫秒为单位)。默认情况下,它不会超时。
#maxIdleTime:一个整数值,用于定义可用会话在删除之前在池中等待的时间(以毫秒为单位)。默认情况下,它不会清理会话。
#----------------------------------------------------------------------------------------------------------------------
host = "192.168.1.207";
port = 33060;
user = "admin";
pwd = "Admin207";
db_name = "park";
pool_max_size = 10;
pool_queue_timeout = 1000;
pool_max_idle_time = 6000;
4.3 ftp client模块
封装了ctk::ftp::Client这个类,实现了ftp的上传功能,其它如下载等ftp功能暂且不考虑,因为目前为止,我的项目还不需要这些功能。
上传有二个重构的函数:
/**@brief Upload cv::Mat to a remote server and save it as a JPG file
* @param image Uploaded CV:: Mat
* @param remoteFile JPG save path for remote server, automatically created if it does not exist
* @return CURLcode(or errno) and error-message
*/
std::tuple<int, std::string> upload(const cv::Mat &image, const std::string &remoteFile);
/**@brief Upload a local file to a remote server
* @param localFile
* @param remoteFile
* @return CURLcode(or errno) and error-message
*/
std::tuple<int, std::string> upload(const std::string &localFile, const std::string &remoteFile) const;
第1个upload函数是上传cv::Mat(内存中)到远程图像服务器并保存为JPG文件。
第2个upload函数是上传本地文件(任意格式的有效文件)到远程图像服务器并保存为同一格式的文件。
优点:
(1) 支持多线程上传:如在一个线程内上传100个JPG文件到远程服务器,可以创建若干个线程并行上传。
(2) 性能优越:上传100个1M左右的人脸图片(JPG格式)到远程图片服务器(局域网内)耗时1秒左右。这是根据<Smart-park-FaceDR-svc>项目试运行一年多得出的结论。
(3) 支持断线续传功能。
实例1------上传本地路径下的JPG文件到远程服务器:
//! 单个文件上传
std::tuple<int, string> ftp_uploadFile(const std::string &localFile, const string &remotePath) {
std::string host = "192.168.1.142";
std::string user = "lin";
std::string pwd = "123456";
//auto pLogger = ctk::log::createLogger();
ctk::ftp::Client client(std::move(host), std::move(user), std::move(pwd));
auto [code, msg] = client.init();
if (code != 0) {
std::cout << msg << std::endl;
spdlog::error(msg);
}
return client.upload(localFile, remotePath);
}
//! 多线程调用ftp_uploadFile函数实现路径下的JPG文件并行上传
int ftp_upload_dir_mt() {
std::string host = "192.168.1.142";
std::string user = "lin";
std::string pwd = "123456";
ctk::ftp::Client client(std::move(host), std::move(user), std::move(pwd));
auto pLogger = ctk::log::createLogger();
const std::string localPath = "/home/plum/repository/cvData/face/snap";
std::string remotePath = "/home/lin/ftp_test_data/snap";
// 使用std::future和std::async来实现多线程上传
std::vector<std::future<std::tuple<int, string>>> results;
// std::cout << "curl_version is :" << curl_version() << std::endl;
std::cout << "Start uploading:" << ctk::utils::now_millis() << std::endl;
for (const auto &entry : fs::directory_iterator(localPath)) {
if (entry.path().extension() == ".JPG") {
std::string filename = entry.path().filename().string();
std::string localFile = entry.path().string();
std::string remoteFile = (remotePath + "/").append(filename);
results.emplace_back(std::async(std::launch::async, ftp_uploadFile, localFile, remoteFile));
}
}
int res = 0;
// 等待所有上传任务完成
for (auto &result: results) {
auto [code1, msg1] = result.get();
if (code1 != 0) {
// 处理错误
std::cerr << "Upload failed with error code: " << code1 << std::endl;
std::cerr << "Error upload image, error msg:" << msg1 << std::endl;
pLogger->info(msg1);
res = 1;
}
}
std::cout << "Stop uploading:" << ctk::utils::now_millis() << std::endl;
client.cleanup();
return res;
}
实例2----------单线程及多线程上传cv::Mat(内存中)到远程服务器并保存为JPG文件
//! 上传cv::Mat实例
std::tuple<int, string> ftp_uploadMat(const string &remotePath, const cv::Mat &image) {
std::string host = "192.168.1.142";
std::string user = "lin";
std::string pwd = "123456";
ctk::ftp::Client client(std::move(host), std::move(user), std::move(pwd));
auto [code, msg] = client.init();
if (code != 0) {
std::cout << msg << std::endl;
}
return client.upload(image, remotePath);
//return res;
}
//! 单线程调用ftp_uploadMat实例
int ftp_uploadMat_single() {
std::string remotePath = "/home/lin/ftp_test_data/";
cv::Mat image = cv::imread("/home/plum/repository/cvData/face/init-image/DSC04599.JPG");
if (image.empty()) {
std::cerr << "Error loading image" << std::endl;
return 1;
}
string remoteFile = remotePath + ctk::utils::filename("DSC04599.JPG");
auto [code, msg] = ftp_uploadMat(remoteFile, image.clone());
if (code != CURLE_OK)
{
std::cerr << "Error upload image, errcode is:" << code << std::endl;
std::cerr << "Error upload image, error msg:" << msg << std::endl;
}
return code;
}
//! 多线程调用ftp_uploadMat实例
int ftp_uploadMat_mt() {
std::vector<std::string> files_to_upload = {
"/home/plum/repository/cvData/face/init-image/DSC04593.JPG",
"/home/plum/repository/cvData/face/init-image/DSC04599.JPG",
"/home/plum/repository/cvData/face/init-image/DSC04765.JPG",
"/home/plum/repository/cvData/face/init-image/DSC04768.JPG"
};
std::string remotePath = "/home/lin/ftp_test_data/";
// 使用std::future和std::async来实现多线程上传
std::vector<std::future<std::tuple<int, string>>> results;
std::cout << "curl_version is :" << curl_version() << std::endl;
std::cout << "Start uploading:" << ctk::utils::now_millis() << std::endl;
for (const auto &pair : files_to_upload) {
cv::Mat image = cv::imread(pair);
if (image.empty()) {
std::cerr << "Error loading image" << std::endl;
continue;
}
string remoteFile = remotePath + ctk::utils::filename(pair);
results.emplace_back(std::async(std::launch::async, ftp_uploadMat, remoteFile, image));
}
// 等待所有上传任务完成
for (auto &result: results) {
auto [code, msg] = result.get();
if (code != CURLE_OK) {
// 处理错误
std::cerr << "Upload failed with error code: " << code << std::endl;
std::cerr << "Error upload image, error msg:" << msg << std::endl;
}
}
std::cout << "Stop uploading:" << ctk::utils::now_millis() << std::endl;
return 0;
}
4.4 cv人脸检测与识别模块
采用opencv4.9的DNN模块封装了class YunetAndSF,实现了人脸检测与识别的所有功能(函数)。
//
// Created by Remon on 2023/2/21.
//
#ifndef CTK_CVFACE_H
#define CTK_CVFACE_H
#include "opencv2/opencv.hpp"
#include "ctk/mysqlx.h"
#include <map>
#include <utility>
#include <vector>
#include <string>
#include <iostream>
#include <chrono>
namespace ctk::face {
/**@brief DNN-based face detector and recognizer
* model download link: https://github.com/opencv/opencv_zoo/tree/master/models/face_detection_yunet
* model download link: https://github.com/opencv/opencv_zoo/tree/master/models/face_recognition_sface
*/
class YunetAndSF {
public:
YunetAndSF() = default;
/**@brief Get face detector pointer
* @return The face detector pointer
*/
virtual cv::Ptr<cv::FaceDetectorYN> getDetectorPtr() = 0;
/**@brief Get face recognizer pointer
* @return The face recognizer pointer
*/
virtual cv::Ptr<cv::FaceRecognizerSF> getRecognizerPtr() = 0;
/**@brief Path of image file to be processed.Usually only one face is used for face registration
* @param filePath Input file
* @param scale The scaling ratio of the input image
* @return false-Cannot read image in input file.
*/
virtual bool setInputImage(const std::string& filePath, float scale) = 0;
/**@brief Image mat to be processed.Usually from the camera
* @param inputImage Input image
* @param scale The scaling ratio of the input image
* @return false-Cannot read image in input file.
*/
virtual bool setInputImage(const cv::Mat& inputImage, float scale) = 0;
/**@brief Get the input image resize(320,320).
* @return
*/
virtual cv::Mat getInputImage() = 0;
/**@brief Get the scaling ratio of the input image
*
* @return
*/
virtual float getScale() = 0;
/**@brief Detects faces in the input image.
* detection results stored in a 2D cv::Mat of shape [num_faces, 15]
*/
virtual void detect() = 0;
/**@brief Get the faces.Contains one or multiple faces
* @return The faces image.stored in a 2D cv::Mat of shape [num_faces, 15]
*/
virtual cv::Mat getFaces() = 0;
/**@brief Extracting feature values.Using this method for a single face
*/
virtual void extract() = 0;
/**@brief Get the feature values.Using this method for a single face
* @return The feature values
*/
virtual cv::Mat getFeature() = 0;
/**@brief Extract features from all faces.Multiple faces using this method
*/
virtual void extractAll() = 0;
/**@brief Get the feature vector.For face recognition containing multiple faces
* @return
*/
virtual std::vector<cv::Mat> getFeatureVec() = 0;
/**@brief Create an instance smart pointer for the YunetAndSF class based on input parameters
*
* @param fd_model
* @param fr_model
* @param size_w
* @param size_h
* @param score_threshold
* @param nms_threshold
* @param top_k
* @param backend_id
* @param target_id
* @return
*/
static cv::Ptr<YunetAndSF> create(const std::string& fd_model,
std::string& fr_model,
int size_w = 320,
int size_h = 320,
float score_threshold = 0.9f,
float nms_threshold = 0.3f,
int top_k = 5000,
int backend_id = 0,
int target_id = 0);
/**@brief Create an instance smart pointer for the YunetAndSF class based on the configuration file
* @return
*/
static cv::Ptr<YunetAndSF> create();
};
class Error {
public:
inline static std::string BAD_FORMAT_IMAGE = "missing file, improper permissions, unsupported or invalid format";
};
}
#endif //CTK_CVFACE_H
实例----读取含有一张人脸的图片,检测出人脸特征并输出(应用于人脸注册):
void cvFaceDR_image(const string& imageFile) {
std::string face_image_root = "/home/plum/repository/cvData/face/snap";
try {
auto ynsf = ctk::face::YunetAndSF::create();
//string input_image_path = "/home/plum/repository/faceds/hsdata/DSC04593.JPG";
if (!ynsf->setInputImage(imageFile, 0.16))
{
// std::cerr << "Cannot read image: " << imageFile << std::endl;
throw ctk::CtkException("cvFace", ctk::face::Error::BAD_FORMAT_IMAGE);
//;return 1;
}
std::string now = std::move(ctk::utils::now_millis());
std::cout << imageFile << " FaceDR start in:" << now << std::endl;
ynsf->detect();
cv::Mat inputImage = ynsf->getInputImage();
cv::Mat faces = ynsf->getFaces();
auto vInputImage = ctk::cvutils::visualize(inputImage, faces, now);
std::string vImage = ctk::utils::filenamePre(imageFile).append("_face.JPG");
string& vImagePath(face_image_root);
vImagePath.append(vImage);
std::cout << "Results are saved to " << vImagePath << std::endl;
cv::imwrite(vImagePath, vInputImage);
ynsf->extract();
cv::Mat feature = ynsf->getFeature();
std::string ftStr = ctk::cvutils::matToStr(feature);
} catch (const std::runtime_error& error) {
std::cerr << error.what() << std::endl;
}
}
其它如检测含有多张人脸的图片,连接网络摄像机实时检测与识别等例子就不在此文列举了。
五、下一步计划
5.1 后续版本规划
可能会新增加以下模块:
模块 | https |
---|---|
https | ssl验证、下载 |
webservice | 轻量级 |
5.2 编写SDK
编写《libctk-SDK》,方便项目组新成员使用。
5.3 考虑开源
现在还有点压力,争取年末在github平台开源。
HI,我是Remon,CSDN上的别名:AncleLeen(被csdn强迫更名),一位30年的老码农了。
VX:RemonLin,敬请备注:cvDNN。VX扫一扫: