其中FaceSearchRecognizeExecuteCB实现了人脸识别:static void FaceSearchRecognizeExecuteCB(napi_env env, void *data)
{
CommandStrData *commandStrData = dynamic_cast<CommandStrData*>((CommandStrData *)data);
if (commandStrData == nullptr) {
HILOG_ERROR("nullptr point!", __FUNCTION__, __LINE__);
return;
}
FaceSearchInfo faceSearch = *(commandStrData->mFaceSearch);
commandStrData->result = FaceSearchSearchRecognizer(faceSearch, commandStrData->filename);
LOGI("Recognize result : %s !", __FUNCTION__, __LINE__, commandStrData->result.c_str());
}
FaceSearchRecognizeCompleteCB函数通过napi_resolve_deferred接口将识别结果返回到应用端。static void FaceSearchRecognizeCompleteCB(napi_env env, napi_status status, void *data)
{
CommandStrData *commandStrData = dynamic_cast<CommandStrData*>((CommandStrData *)data);
napi_value result;
if (commandStrData == nullptr || commandStrData->deferred == nullptr) {
LOGE("nullptr", __FUNCTION__, __LINE__);
if (commandStrData != nullptr) {
napi_delete_async_work(env, commandStrData->asyncWork);
delete commandStrData;
}
return;
}
const char *result_str = (const char *)commandStrData->result.c_str();
if (napi_create_string_utf8(env, result_str, strlen(result_str), &result) != napi_ok) {
LOGE("napi_create_string_utf8 failed!", __FUNCTION__, __LINE__);
napi_delete_async_work(env, commandStrData->asyncWork);
delete commandStrData;
return;
}
napi_resolve_deferred(env, commandStrData->deferred, result);
napi_delete_async_work(env, commandStrData->asyncWork);
delete commandStrData;
}
通过人脸特征提取与比对模块,对传入的数据与已注册的数据进行对比,并通过返回对比的相似度来进行判断当前人脸是否为可识别的,最后返回识别结果。具体实现代码:static string FaceSearchSearchRecognizer(FaceSearchInfo &info, string filename)
{
if (info.engine == nullptr) {
cerr << "NULL POINT!" << endl;
return "recognize error 0";
}
string name;
float threshold = 0.7f;
seeta:
ualityAssessor QA;
auto frame = cv::imread(filename);
if (frame.empty()) {
LOGE("read image %{public}s failed!", filename.c_str());
return "recognize error 1!";
}
seeta::cv::ImageData image = frame;
std::vector<SeetaFaceInfo> faces = info.engine->DetectFaces(image);
for (SeetaFaceInfo &face : faces) {
int64_t index = 0;
float similarity = 0;
auto points = info.engine->DetectPoints(image, face);
auto score = QA.evaluate(image, face.pos, points.data());
if (score == 0) {
name = "ignored";
} else {
auto queried = info.engine->QueryTop(image, points.data(), 1, &index, &similarity);
// no face queried from database
if (queried < 1) continue;
// similarity greater than threshold, means recognized
if( similarity > threshold ) {
name = info.GalleryIndexMap[index];
}
}
}
LOGI("name : %{public}s \n", name.length() > 0 ? name.c_str() : "null");
return name.length() > 0 ? name : "recognize failed";
}
至此,所有的NAPI接口已经开发完成。
5. NAPI库编译开发完NAPI接口后,我们需要将我们编写的库加入到系统中进行编译,我们需要添加一个自己的子系统。首先在库目录下新建一个ohos.build文件并添加以下代码:
{
"subsystem": "SeetafaceApp",
"parts": {
"SeetafaceApi": {
"module_list": [
"//seetaface:seetafaceapp_napi"
],
"test_list": [ ]
}
}
}
其次同一目录新建一个BUILD.gn,将库源文件以及对应的依赖加上,如下:import("//build/ohos.gni")
config("lib_config") {
cflags_cc = [
"-frtti",
"-fexceptions",
"-DCVAPI_EXPORTS",
"-DOPENCV_ALLOCATOR_STATS_COUNTER_TYPE=int",
"-D_USE_MATH_DEFINES",
"-D__OPENCV_BUILD=1",
"-D__STDC_CONSTANT_MACROS",
"-D__STDC_FORMAT_MACROS",
"-D__STDC_LIMIT_MACROS",
"-O2",
"-Wno-error=header-hygiene",
}
ohos_shared_library("seetafaceapp_napi") {
sources = [
"app.cpp",
include_dirs = [
"./",
"//third_party/opencv/include",
"//third_party/opencv/common",
"//third_party/opencv/modules/core/include",
"//third_party/opencv/modules/highgui/include",
"//third_party/opencv/modules/imgcodecs/include",
"//third_party/opencv/modules/imgproc/include",
"//third_party/opencv/modules/calib3d/include",
"//third_party/opencv/modules/dnn/include",
"//third_party/opencv/modules/features2d/include",
"//third_party/opencv/modules/flann/include",
"//third_party/opencv/modules/ts/include",
"//third_party/opencv/modules/video/include",
"//third_party/opencv/modules/videoio/include",
"//third_party/opencv/modules/ml/include",
"//third_party/opencv/modules/objdetect/include",
"//third_party/opencv/modules/photo/include",
"//third_party/opencv/modules/stitching/include",
"//third_party/SeetaFace2/FaceDetector/include",
"//third_party/SeetaFace2/FaceLandmarker/include",
"//third_party/SeetaFace2/FaceRecognizer/include",
"//third_party/SeetaFace2/QualityAssessor/include",
"//base/accessibility/common/log/include",
"//base/hiviewdfx/hilog_lite/interfaces/native/innerkits"
deps = [ "//foundation/ace/napi:ace_napi" ]
deps += [ "//third_party/opencv:opencv" ]
deps += [ "//third_party/SeetaFace2:SeetaFace2" ]
external_deps = [
"hiviewdfx_hilog_native:libhilog",
configs = [
":lib_config"
# 指定库生成的路径
relative_install_dir = "module"
# 子系统及其组件,后面会引用
subsystem_name = "SeetafaceApp"
part_name = "SeetafaceApi"
}
添加完对应的文件后,我们需要将我们的子系统添加到系统中进行编译,打开build/subsystem_config.json并在最后添加以下代码: "SeetafaceApp": {
"path": "seetaface",
"name": "SeetafaceApp"
}
添加完子系统再修改产对应的品配置打开productdefine/common/products/rk3568.json并在最后添加以下代码:"SeetafaceApp:SeetafaceApi":{}
做完以上修改后我们就可以通过以下命令直接编译NAPI的库文件了:./build.sh --product-name rk3568 --ccache
参考RK3568快速上手-镜像烧录完成烧录即可。
应用端开发
在完成设备NAPI功能开发后,应用端通过调用NAPI组件中暴露给应用的人脸识别接口,即可实现对应功能。接下来就带着大家使用NAPI实现人脸识别功能。
开发准备
1. 下载DevEco Studio 3.0 Beta4;
2. 搭建开发环境,参考开发准备;
3. 了解属性eTS开发,参考eTS语言快速入门;
SeetaFace2初始化
1. 首先将SeetaFace2 NAPI接口声明文件放置于SDK目录/api下;
2. 然后导入SeetaFace2 NAPI模块;ck-start/star
3. 调用初始化接口;
// 首页实例创建后
async aboutToAppear() {
await StorageUtils.clearModel();
CommonLog.info(TAG,'aboutToAppear')
// 初始化人脸识别
let res = SeetafaceApp.FaceSearchInit()
CommonLog.info(TAG,`FaceSearchInit res=${res}`)
this.requestPermissions()
}
// 请求权限
requestPermissions(){
CommonLog.info(TAG,'requestPermissions')
let context = featureAbility.getContext()
context.requestPermissionsFromUser(PERMISSIONS, 666,(res)=>{
this.getMediaImage()
})
}
获取所有人脸图片
通过文件管理模块fileio和媒体库管理mediaLibrary,获取指定应用数据目录下所有的图片信息,并将路径赋值给faceList,faceList数据用于Image组件提供url进行加载图片。
// 获取所有图片
async getMediaImage(){
let context = featureAbility.getContext();
// 获取本地应用沙箱路径
let localPath = await context.getOrCreateLocalDir()
CommonLog.info(TAG, `localPath
{localPath}`)
let facePath = localPath + "/files"
// 获取所有照片
this.faceList = await FileUtil.getImagePath(facePath)
}
设置人脸模型
获取选中的人脸图片地址和输入的名字,调用SeetafaceApp.FaceSearchRegister(params)进行设置人脸模型。其中参数params由name名字、image图片地址集合和sum图片数量组成。async submit(name) {
if (!name || name.length == 0) {
CommonLog.info(TAG, 'name is empty')
return
}
let selectArr = this.faceList.filter(item => item.isSelect)
if (selectArr.length == 0) {
CommonLog.info(TAG, 'faceList is empty')
return
}
// 关闭弹窗
this.dialogController.close()
try {
let urls = []
let files = []
selectArr.forEach(item => {
let source = item.url.replace('file://', '')
CommonLog.info(TAG, `source
{source}`)
urls.push(item.url)
files.push(source)
})
// 设置人脸识别模型参数
let params = {
name: name,
image: files,
sum: files.length
}
CommonLog.info(TAG, 'FaceSearchRegister' + JSON.stringify(params))
let res = SeetafaceApp.FaceSearchRegister(params)
CommonLog.info(TAG, 'FaceSearchRegister res ' + res)
// 保存已设置的人脸模型到轻量存储
let data = {
name:name,
urls:urls
}
let modelStr = await StorageUtils.getModel()
let modelList = JSON.parse(modelStr)
modelList.push(data)
StorageUtils.setModel(modelList)
router.back()
} catch (err) {
CommonLog.error(TAG, 'submit fail ' + err)
}
}
实现框选人脸
调用SeetafaceApp.GetRecognizePoints传入当前图片地址,获取到人脸左上和右下坐标,再通过CanvasRenderingContext2D对象绘画出人脸框。// 框选当前人脸
select(){
try{
CommonLog.info(TAG,`select start`)
// 获取人脸左上和右下坐标
let res = SeetafaceApp.GetRecognizePoints(this.url)
CommonLog.info(TAG,`select start1` + JSON.stringify(res))
let points = res.recognizeFrame as Array<number>
CommonLog.info(TAG,`select success ${JSON.stringify(res)}`)
let faceNumber = Math.floor(points.length/4)
// 框选出所有人脸
this.context.lineWidth = 5
this.context.strokeStyle = Color.Yellow.toString()
for (let index = 0; index < faceNumber; index++) {
let baseNumber = 4 * index
this.context.strokeRect(points[baseNumber],points[1 + baseNumber],points[2 + baseNumber],points[3 + baseNumber])
this.context.save()
}
CommonLog.info(TAG,`strokeRect success `)
}catch(err){
CommonLog.error(TAG,`select fail ${JSON.stringify(err)}`)
}
}
实现人脸识别
调用SeetafaceApp.FaceSearchGetRecognize(url),传入图片地址对人脸进行识别并返回对应识别出来的名字。// 人脸识别
recognize(){
SeetafaceApp.FaceSearchGetRecognize(this.url).then(res=>{
CommonLog.info(TAG,'recognize suceess' + JSON.stringify(res))
if(res && res != 'ignored' && res != "recognize failed" && res != 'recognize error 1!'){
// 赋值识别到的人物模型
this.name = res
}else{
this.name = '未识别到该模型'
}
}).catch(err=>{
CommonLog.error(TAG,'recognize' + err)
this.name = '未识别到该模型'
})
}
参考文档
SeetaFace2移植开发文档:
https://gitee.com/openharmony-sig/knowledge_demo_smart_home/blob/master/docs/SeetaFace2/%E4%BA%BA%E8%84%B8%E8%AF%86%E5%88%AB%E5%BA%93%E7%9A%84%E7%A7%BB%E6%A4%8D.md
OpenHarmony中napi的开发视频教程:
https://www.bilibili.com/video/BV1L44y1p7KE?spm_id_from=333.999.0.0
RK3568快速上手:
https://growing.openharmony.cn/mainPlay/learnPathMaps?id=27
人脸识别应用:
https://gitee.com/openharmony-sig/knowledge_demo_travel/blob/master/docs/FaceRecognition_ETS/README_zh.md
应用开发准备:
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/quick-start/start-overview.md/
eTS语言快速入门:
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/quick-start/start-with-ets.md/
知识体系工作组:
https://gitee.com/openharmony-sig/knowledge