如何在OpenHarmony上使用SeetaFace2人脸识别库?

news2024/11/20 6:16:51

简介

相信大部分同学们都已了解或接触过OpenAtom OpenHarmony(以下简称“OpenHarmony”)了,但你一定没在OpenHarmony上实现过人脸识别功能,跟着本文带你快速在OpenHarmony标准设备上基于SeetaFace2和OpenCV实现人脸识别。

项目效果

本项目实现了导入人脸模型、人脸框选和人脸识别三大功能,操作流程如下:

1. 录入页面点击右下角按钮,跳转拍摄页面进行拍照;

2. 选择一张或多张人脸作为训练模型,并设置对应的名字;

3. 选择一张未录入的人脸图片,点击框选按钮实现人脸图片框选功能;

4. 最后点击识别,应用会对当前图片进行匹配,最终在界面中显示识别结果。

快速上手

设备端开发

设备端通过OpenCV对图像进行处理并通过Seetaface2对图形数据进行人脸头像的识别,最终输出对应的NAPI接口提供给应用端调用。因此设备端开发主要涉及到OpenCV和Seetaface2的移植以及NAPI接口的开发。

OpenCV库移植

OpenCV是一个功能非常强大的开源计算机视觉库。此库已由知识体系工作组移植到了OpenHarmony中,后期还会将此库合入到主仓。在此库上主仓之前,我们只需要以下几个步骤就可以实现OpenCV的移植使用。

1. 通过以下命令下载已经移植好的OpenCV

git clone git@gitee.com:zhong-luping/ohos_opencv.git

2. 将OpenCV拷贝到OpenHarmony目录的third_party下

cp -raf opencv ~/openharmony/third_party/

3. 适当裁剪编译选项

打开OpenCV目录下的BUILD.gn,如下:

不需要video以及flann功能,将对应的模块注释即可。

import("//build/ohos.gni")
group("opencv") {
    deps = [
        "//third_party/opencv/modules/core:opencv_core",
      //  "//third_party/opencv/modules/flann:opencv_flann",
        "//third_party/opencv/modules/imgproc:opencv_imgproc",
        "//third_party/opencv/modules/ml:opencv_ml",
        "//third_party/opencv/modules/photo:opencv_photo",
        "//third_party/opencv/modules/dnn:opencv_dnn",
        "//third_party/opencv/modules/features2d:opencv_features2d",
        "//third_party/opencv/modules/imgcodecs:opencv_imgcodecs",
        "//third_party/opencv/modules/videoio:opencv_videoio",
        "//third_party/opencv/modules/calib3d:opencv_calib3d",
        "//third_party/opencv/modules/highgui:opencv_highgui",
        "//third_party/opencv/modules/objdetect:opencv_objdetect",
        "//third_party/opencv/modules/stitching:opencv_stitching",
        "//third_party/opencv/modules/ts:opencv_ts",
     //   "//third_party/opencv/modules/video:opencv_video",
       "//third_party/opencv/modules/gapi:opencv_gapi",
    ]
}

4. 添加依赖子系统的part_name,编译框架子系统会将编译出的库拷贝到系统文件中。

此项目中我们新建了一个SeetaFaceApp的子系统,该子系统中命名part_name为SeetafaceApi,所以我们需要在对应模块中的BUILD.gn中加上part_name=“SeetafaceApi”

以module/core为例:

ohos_shared_library("opencv_core"){
 sources = [ ... ]
configs = [  ... ]
deps = [ ... ]
part_name = "SeetafaceApi"
}

5. 编译工程需要添加OpenCV的依赖。

在生成NAPI的BUILD.gn中添加以下依赖:

deps += [ "//third_party/opencv:opencv" ]

至此,人脸识别中OpenCV的移植使用完成。

SeetaFace2库移植

SeetaFace2是中科视拓开源的第二代人脸识别库。包括了搭建一套全自动人脸识别系统所需的三个核心模块,即:人脸检测模块FaceDetector、面部关键点定位模块FaceLandmarker以及人脸特征提取与比对模块 FaceRecognizer。

关于SeetaFace2的移植请参照文档:SeetaFace2移植开发文档。

NAPI接口开发

关于OpenHarmony中的NAPI开发,参考视频:

OpenHarmony中napi的开发视频教程。本文将重点讲解NAPI接口如何实现OpenCV以及SeetaFace的调用。

1. 人脸框获取的NAPI接口的实现。

int GetRecognizePoints(const char *image_path);

此接口主要是通过应用层输入一张图片,通过OpenCV的imread接口获取到图片数据,并通过人脸检测模块FaceDetector分析获得图片中所有的人脸矩形框(矩形框是以x,y,w,h的方式)并将人脸框矩形以数组的方式返回到应用层。

人脸框矩形获取的主要代码如下:

static int RecognizePoint(string image_path, FaceRect *rect, int num)
{
    if (rect == nullptr) {
        cerr << "NULL POINT!" << endl;
        LOGE("NULL POINT! \n");
        return -1;
    }
    seeta::ModelSetting::Device device = seeta::ModelSetting::CPU;
    int id = 0;
 
 
    /* 设置人脸识别模型。*/
    seeta::ModelSetting FD_model( "/system/usr/model/fd_2_00.dat", device, id );
    seeta::ModelSetting FL_model( "/system/usr/model/pd_2_00_pts81.dat", device, id );
 
 
    seeta::FaceDetector FD(FD_model);
    seeta::FaceLandmarker FL(FL_model);
 
 
    FD.set(seeta::FaceDetector::PROPERTY_VIDEO_STABLE, 1);
 
 
    /* 读取图片数据 */
    auto frame = imread(image_path);
    seeta::cv::ImageData simage = frame;
    if (simage.empty()) {
        cerr << "Can not open image: " << image_path << endl;
        LOGE("Can not open image: %{public}s", image_path.c_str());
        return -1;
    }
    /* 图片数据进行人脸识别处理 ,获取所有的人脸框数据对象*/
    auto faces = FD.detect(simage);
    if (faces.size <= 0) {
        cerr << "detect " << image_path << "failed!" << endl;
        LOGE("detect image: %s failed!", image_path.c_str());
        return -1;
    }
    for (int i = 0; (i < faces.size && i < num); i++) {
        /* 将所有人脸框对象数据以坐标形式输出*/
        auto &face = faces.data[i];
        memcpy(&rect[i], &(face.pos), sizeof(FaceRect));
    }
    return faces.size;
}

其中FD_model是人脸检测模型,而FL_model是面部关键点定位模型(此模型分为5点定位和81点定位,本项目中使用的是81点定位模型),这些模型从开源项目中免费获取。

通过以上方式获取到对应的人脸矩形框后,再将矩形框以数组的方式返回到应用端:

string image = path;
p = (FaceRect *)malloc(sizeof(FaceRect) * MAX_FACE_RECT);
/* 根据图片进行人脸识别并获取人脸框坐标点 */
int retval = RecognizePoint(image, p, MAX_FACE_RECT);
if (retval <= napi_ok) {
    LOGE("GetNapiValueString failed!");
    free(p);
    return result;
} 
/*将所有坐标点以数组方式返回到应用端*/
for (int i = 0; i < retval; i++) {
    int arry_int[4] = {p[i].x, p[i].y, p[i].w, p[i].h};
    int arraySize = (sizeof(arry_int) / sizeof(arry_int[0]));
    for (int j = 0; j < arraySize; j++) {
        napi_value num_val;
        if (napi_create_int32(env, arry_int[j], &num_val) != napi_ok) {
            LOGE("napi_create_int32 failed!");
            return result;
        }
        napi_set_element(env, array, i*arraySize + j, num_val);
    }
}
if (napi_create_object(env, &result) != napi_ok) {
    LOGE("napi_create_object failed!");
    free(p);
    return result;
}
if (napi_set_named_property(env, result, "recognizeFrame", array) != napi_ok) {
    LOGE("napi_set_named_property failed!");
    free(p);
    return result;
}
LOGI("");
free(p);
return result;

其中array是通过napi_create_array创建的一个NAPI数组对象,通过 napi_set_element将所有的矩形框数据保存到array对象中,最后通过 napi_set_named_property将array转换成应用端可识别的对象类型result并将其返回。

2. 人脸搜索识别初始化与逆初始化。

1. int FaceSearchInit();

2. int FaceSearchDeinit();

这2个接口主要是提供给人脸搜索以及识别调用的,初始化主要包含模型的注册以及识别模块的初始化:

static  int FaceSearchInit(FaceSearchInfo *info)
{
    if (info == NULL) {
        info = (FaceSearchInfo *)malloc(sizeof(FaceSearchInfo));
        if (info == nullptr) {
            cerr << "NULL POINT!" << endl;
            return -1;
        }
    }
 
 
    seeta::ModelSetting::Device device = seeta::ModelSetting::CPU;
    int id = 0;
    seeta::ModelSetting FD_model( "/system/usr/model/fd_2_00.dat", device, id );
    seeta::ModelSetting PD_model( "/system/usr//model/pd_2_00_pts5.dat", device, id );
    seeta::ModelSetting FR_model( "/system/usr/model/fr_2_10.dat", device, id );
 
 
    info->engine = make_shared<seeta::FaceEngine>(FD_model, PD_model, FR_model, 2, 16);
    info->engine->FD.set( seeta::FaceDetector::PROPERTY_MIN_FACE_SIZE, 80);
 
 
    info->GalleryIndexMap.clear();
 
 
    return 0;
}

而逆初始化就是做一些内存的释放。

static void FaceSearchDeinit(FaceSearchInfo *info, int need_delete)
{
    if (info != nullptr) {
        if (info->engine != nullptr) {
        }
 
 
        info->GalleryIndexMap.clear();
        if (need_delete) {
            free(info);
            info = nullptr;
        }
    }
}

3. 人脸搜索识别注册接口的实现。

int FaceSearchRegister(const char *value);

需要注意的是,该接口需要应用端传入一个json数据的参数,主要包含注册人脸的名字,图片以及图片个数,如{“name”:“刘德华”,“sum”:“2”,“image”:{“11.jpg”,“12.jpg”}}。而解析参数的时候需要调用 napi_get_named_property对json数据的各个对象进行解析,具体代码如下:

napi_get_cb_info(env, info, &argc, &argv, &thisVar, &data);
napi_value object = argv;
napi_value value = nullptr;
 
 
if (napi_get_named_property(env, object, (const char *)"name", &value) == napi_ok) {
    char name[64] = {0};
    if (GetNapiValueString(env, value, (char *)name, sizeof(name)) < 0) {
        LOGE("GetNapiValueString failed!");
        return result;
    }
    reg_info.name = name;
}
LOGI("name = %{public}s", reg_info.name.c_str());
if (napi_get_named_property(env, object, (const char *)"sum", &value) == napi_ok) {
     
    if (napi_get_value_uint32(env, value, &sum) != napi_ok) {
        LOGE("napi_get_value_uint32 failed!");
        return result;
    }
}
LOGI("sum = %{public}d", sum);
if (napi_get_named_property(env, object, (const char *)"image", &value) == napi_ok) {
    bool res = false;
    if (napi_is_array(env, value, &res) != napi_ok || res == false) {
        LOGE("napi_is_array failed!");
        return result;
    }
    for (int i = 0; i < sum; i++) {
        char image[256] = {0};
        napi_value imgPath = nullptr;
        if (napi_get_element(env, value, i, &imgPath) != napi_ok) {
            LOGE("napi_get_element failed!");
            return result;
        }
        if (GetNapiValueString(env, imgPath, (char *)image, sizeof(image)) < 0) {
            LOGE("GetNapiValueString failed!");
            return result;
        }
        reg_info.path = image;
        if (FaceSearchRegister(g_FaceSearch, reg_info) != napi_ok) {
            retval = -1;
            break;
        }
    }
}

通过napi_get_cb_info获取从应用端传来的参数,并通过 napi_get_named_property获取对应的name以及图片个数,最后通过napi_get_element获取图片数组中的各个image,将name和image通过FaceSearchRegister接口将图片和名字注册到SeetaFace2模块的识别引擎中。具体实现如下:

static int FaceSearchRegister(FaceSearchInfo &info, RegisterInfo &gegister)
{
    if (info.engine == nullptr) {
        cerr << "NULL POINT!" << endl;
        return -1;
    }
 
 
    seeta::cv::ImageData image = cv::imread(gegister.path);
    auto id = info.engine->Register(image);
    if (id >= 0) {
        info.GalleryIndexMap.insert(make_pair(id, gegister.name));
    }
 
 
    return 0;
}

注册完数据后,后续可以通过该引擎来识别对应的图片。

4. 获取人脸搜索识别结果接口的实现。

char *FaceSearchGetRecognize(const char *image_path);

该接口实现了通过传入一张图片,在识别引擎中进行搜索识别。如果识别引擎中有类似的人脸注册,则返回对应人脸注册时的名字,否则返回不识别(ignored)字样。该方法是通过异步回调的方式实现的:

// 创建async work,创建成功后通过最后一个参数(commandStrData->asyncWork)返回async work的handle
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "FaceSearchGetPersonRecognizeMethod", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, FaceSearchRecognizeExecuteCB, FaceSearchRecognizeCompleteCB,
        (void *)commandStrData, &commandStrData->asyncWork);
 
 
// 将刚创建的async work加到队列,由底层去调度执行
napi_queue_async_work(env, commandStrData->asyncWork);

其中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::QualityAssessor 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对象绘画出人脸框。

实现人脸识别

调用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 = '未识别到该模型'
    })
}

为了帮助到大家能够更有效的学习OpenHarmony 开发的内容,下面特别准备了一些相关的参考学习资料:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1686992.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++ 实现 梯度下降线性回归模型

理论与python实现部分 3.1. 线性回归 — 动手学深度学习 2.0.0 documentation c代码 没能力实现反向传播求梯度&#xff0c;只能自己手动算导数了 #include <bits/stdc.h> #include <time.h> using namespace std;//y_hat X * W b // linreg 函数&#xff1a…

9 个适用于小型企业的顶级API管理解决方案

应用程序接口管理解决方案可帮助各种规模的企业开发、部署和管理其应用程序接口&#xff0c;并实现收入最大化。 建立 API 的组织和开发人员可能会被整个 API 生命周期中需要完成的大量任务压得喘不过气来。从规划和构建到部署、维护和货币化&#xff1b;这是一项具有挑战性的工…

【计算机网络原理】对传输层TCP协议的重点知识的总结

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

7.从0做一个vue键盘组件

文章目录 1. 从0做一个键盘组件1.1. 最终效果1.2. 分析1.3. 实现1.4. 如何引用 1. 从0做一个键盘组件 首先是why的问题&#xff1a;为什么需要做键盘组件&#xff1f; 我们目前可知的场景&#xff1a; 在新增账单的时候&#xff0c;需要用到键盘在比如从账单列表页&#xff…

2024年 电工杯 (B题)大学生数学建模挑战赛 | 大学生平衡膳食食谱的优化设计 | 数学建模完整代码解析

DeepVisionary 每日深度学习前沿科技推送&顶会论文&数学建模与科技信息前沿资讯分享&#xff0c;与你一起了解前沿科技知识&#xff01; 本次DeepVisionary带来的是电工杯的详细解读&#xff1a; 完整内容可以在文章末尾全文免费领取&阅读&#xff01; 问题1&…

【Python自动化测试】:Unittest单元测试与HTMLTestRunner自动生成测试用例的好帮手

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 &#x1f525;前言&#x1f680;unittest编写测试用例&#x1f680;unittest测…

49 序列化和反序列化

本章重点 理解应用层的作用&#xff0c;初识http协议 理解传输层的作用&#xff0c;深入理解tcp的各项特性和机制 对整个tcp/ip协议有系统的理解 对tcp/ip协议体系下的其他重要协议和技术有一定的了解 学会使用一些网络问题的工具和方法 目录 1.应用层 2.协议概念 3. 网络计…

awesome-ai4s 现已开源!超全 AI for Science 学术论文与数据资源汇总,持续更新ing

2018 年中国科学院院士鄂维南提出「AI for Science」概念&#xff0c;强调利用 AI 学习科学原理、创造科学模型来解决实际问题。同年&#xff0c;AlphaFold 崭露头角&#xff0c;从 43 种蛋白质中准确预测出了 25 种蛋白质结构。2021 年&#xff0c;AlphaFold 2 开源并预测了 9…

缓存降级

当Redis缓存出现问题或者无法正常工作时,需要有一种应对措施,避免直接访问数据库而导致整个系统瘫痪。缓存降级就是这样一种机制。 主要的缓存降级策略包括: 本地缓存降级 当Redis缓存不可用时,可以先尝试使用本地进程内缓存,如Guava Cache或Caffeine等。这样可以减少对Redis…

OpenLayers中实现对ImageStatic图层的扩展以支持平铺WrapX功能

地图平铺技术概述 地图平铺&#xff08;Tiling&#xff09;是一种将大尺寸地图数据分割成小块&#xff08;瓦片&#xff09;的技术&#xff0c;这在地图服务中非常常见。它使得地图数据能高效加载和展示&#xff0c;尤其适合网络环境。通过仅加载当前视图窗口所需的地图瓦片&a…

Qt官方示例---opengl

文件相对路径&#xff1a;Examples\Qt-5.9.1\opengl 2dpainting cube computegles31 contextinfo hellogl2 hellowindow paintedwindow qopenglwidget qopenglwindow textures threadedqopenglwidget

Rabbitmq 搭建使用案例 [附源码]

Rabbitmq 搭建使用案例 文章目录 RabbitMQ搭建docker 代码golang生产者消费者 可视化消费进度 RabbitMQ搭建 docker docker run -d --hostname rabbitmq --name rabbitmq -e RABBITMQ_DEFAULT_USERadmin -e RABBITMQ_DEFAULT_PASSadmin -e RABBITMQ_DEFAULT_VHOSTmy_vhost -e…

重组蛋白表达系统优缺点对比|卡梅德生物

重组蛋白是现代生物技术中不可或缺的一部分&#xff0c;它们广泛应用于药物开发、研究工具和工业酶的生产。根据目标蛋白的特性和所需的修饰&#xff0c;可以选择不同的表达系统。下文罗列一下四个主要蛋白表达系统的优缺点&#xff1a; 1. 原核表达系统&#xff08;如大肠杆菌…

【QT实战】汇总导航

✨Welcome 大家好&#xff0c;欢迎来到瑾芳玉洁的博客&#xff01; &#x1f611;励志开源分享诗和代码&#xff0c;三餐却无汤&#xff0c;顿顿都被噎。 &#x1f62d;有幸结识那个值得被认真、被珍惜、被捧在手掌心的女孩&#xff0c;不出意外被敷衍、被唾弃、被埋在了垃圾堆…

深度学习之基于Tensorflow卷积神经网络(CNN)实现猫狗识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 在人工智能和深度学习的热潮中&#xff0c;图像识别是一个备受关注的领域。猫狗识别作为图像识…

Milvus的内存索引

简介&#xff1a; 这篇文章主要介绍milvus支持的各种内存索引&#xff0c;以及它们最适用的场景&#xff0c;还有用户为了获得更好的搜索性能可以配置的参数。 索引是有效组织数据的过程&#xff0c;它的主要角色是在大的数据集中显著的加速耗时的查询从而有效的进行相似搜索…

【制作100个unity游戏之28】花半天时间用unity复刻童年4399经典小游戏《黄金矿工》(附带项目源码)

最终效果 文章目录 最终效果前言素材模拟绳子钩子来回摆动发射回收钩子方法发射钩子回收钩子勾取物品随机生成物品其他源码完结 前言 在游戏发展史上&#xff0c;有些游戏以其简单而耐玩的特性&#xff0c;深深地烙印在了玩家的记忆中。《黄金矿工》就是其中之一&#xff0c;它…

SpringBootWeb 篇-深入了解 Mybatis 删除、新增、更新、查询的基础操作与 SQL 预编译解决 SQL 注入问题

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Mybatis 的基础操作 2.0 基础操作 - 环境准备 3.0 基础操作 - 删除操作 3.1 SQL 预编译 3.2 SQL 预编译的优势 3.3 参数占位符 4.0 基础操作 - 新增 4.1 主键返回…

每周节省7800万工时!ChatGPT等成美国降本增效利器

5月23日&#xff0c;全球最大教育、商业出版社之一的Pearson plc在官网发布了&#xff0c;ChatGPT等生成式AI如何帮助人们提升工作效率节省时间的深度研究报告。 该报告一共分析了美国、英国、澳大利亚、巴西和印度5个国家。到2026年&#xff0c;美国节省的时间最多&#xff0…