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

简介

相信大部分同学们都已了解或接触过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 databaseif (queried < 1) continue;// similarity greater than threshold, means recognizedif( 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.mzph.cn/news/839820.shtml

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

相关文章

【Vue】Vue2路由

目录 路由作用Vue Router路由Vue Router路由的组成VueRouter常用的函数Vue Router的使用安装Vue Router创建router引入router使用 备注 Vue多级路由&#xff08;嵌套路由&#xff09;编写组件配置嵌套路由 Vue中的动态路由代码示例父组件Home.vue子组件路由配置 路由的 query 参…

黑龙江等保测评深入理解

“没有网络安全&#xff0c;就没有国家安全”&#xff0c;等级保护测评是指按照网络安全系统制定的一系列的防护过程&#xff0c;对已经有的和即将上线的商业服务的基础设施&#xff08;系统&#xff0c;数据库&#xff0c;中间件等&#xff09;所做的一系列的检查&#xff0c;…

HeyGen AI是什么?怎样使用HeyGen AI?

在数字时代&#xff0c;视频内容为王。无论是在社交媒体还是网站上&#xff0c;视频都以其独特的方式吸引着人们的眼球。然而&#xff0c;制作出专业水准的视频往往需要大量的时间和技术知识。HeyGen AI正是为了解决这一难题而诞生的。 HeyGen AI简介 HeyGen AI是一个创新的视…

618值得买的好物清单,这些数码好物你千万不能错过!

​随着618购物节的距离越来越近&#xff0c;你是不是已经开始疯狂浏览购物app&#xff0c;准备大肆采购一番了&#xff1f;但是在购物之前&#xff0c;还是得先做一做功课&#xff0c;避免陷入购物陷阱&#xff0c;而作为一名经验丰富的数码爱好者&#xff0c;想通过这次机会给…

Thinkphp内核开发盲盒商城源码v2.0 对接易支付/阿里云短信/七牛云存储

源码简介 这套系统是我从以前客户手里拿到的,100完整可用,今天测试防红链接失效了,需要修改防红API即可!前端页面展示我就不放了,懂的都懂 优点是Thinkphp开发的&#xff0c;二开容易。 源码图片 资源获取&#xff1a;Thinkphp内核开发盲盒商城源码v2.0 对接易支付/阿里云短…

kafka监控配置和告警配置——筑梦之路

kafka_exporter项目地址&#xff1a;https://github.com/danielqsj/kafka_exporter docker-compose部署kafka_exporter # docker-compose部署多个kafka_exporter&#xff0c;每个exporter对接一个kafka# cat docker-compose.ymlversion: 3.1 services:kafka-exporter-opslogs…

3DMax文件打开跳出请求操作需要提升

解决方法如下 打开autoremove&#xff0c;点击扩展功能&#xff0c;点击管理员已经阻止运行此应用 提示修复成功后&#xff0c;重启电脑再尝试打开max文件。

保研笔试复习——nju

文章目录 一、单选计算机网络计算机组成原理数字逻辑电路数据结构操作系统微机系统 多选题计算机网络计算机系统结构操作系统 免责声明&#xff1a;题目源自于网络&#xff0c;侵删。 就在今天2024-5-18&#xff0c;考的题下面的只有一道AVL的原题&#xff0c;其他都不是原题&a…

平板如何实现无纸化会议

为了实现高效的无纸化会议&#xff0c;连通宝可以是在内部网络部署&#xff0c;那么&#xff0c;平板如何实现无纸化会议&#xff1f; 1. 服务器配置&#xff1a; 部署专用无纸化会议系统服务器&#xff08;如rhub无纸化会议服务器&#xff09;至组织的内部网络中。确保该服务…

Ipad air6买什么电容笔?5款超值精品平替电容笔推荐!

电容笔作为ipad的最佳拍档&#xff0c;为学生党和打工人带来了极大的便利&#xff0c;二者搭配效率真的大大提升&#xff0c;但是&#xff0c;如何选购一支适合自己的电容笔呢&#xff1f;作为一个对数码设备非常感兴趣并且有一定了解的人&#xff0c;我根据自己多年的使用经验…

SQLite数据库免改造透明加密解决方案:给数据加把锁

在数字化时代&#xff0c;信息安全和隐私保护显得尤为重要。TDE透明加密技术&#xff0c;是一种在用户无感知的情况下对数据进行加密和解密的技术。它能够在数据生成、存储、传输和使用过程中自动进行加密处理&#xff0c;无需用户手动操作。透明加密技术的核心在于其透明性&am…

Wireshark 4.2.5:发现 QUIC 和 VXLAN 协议的新功能

Wireshark 是一种先进且广泛使用的网络协议分析仪&#xff0c;最近发布了新版本 4.2.5&#xff0c;它提供了许多新功能和改进。 Wireshark 4.2.5 发行说明 什么是 Wireshark&#xff1f; Wireshark 是世界上最流行的网络协议分析器。它用于故障排除、分析、开发和教育。 Wiresh…

服务器监控运维方案,一体化智能观测服务器状态

随着信息技术发展&#xff0c;服务器已经成为支撑各类应用系统的核心基础设施。业务数量的日益增长和稳定运行的高要求&#xff0c;也给服务器的稳定性与可靠性建立了更高的标准。然而&#xff0c;传统的服务器管理方式往往难以发现潜在问题&#xff0c;导致故障预警与处置的滞…

【全开源】填表统计预约打卡表单系统FastAdmin+ThinkPHP+UniApp

简化流程&#xff0c;提升效率 一、引言&#xff1a;传统表单处理的局限性 在日常工作和生活中&#xff0c;我们经常会遇到需要填写表单、统计数据和预约打卡等场景。然而&#xff0c;传统的处理方式往往效率低下、易出错&#xff0c;且不利于数据的统计和分析。为了解决这些…

语义化版本规范

Releases 是指软件或项目的正式发布版本&#xff0c;在浏览一些开源仓库时&#xff0c;可以看到当前项目最新版本和历史版本 仔细研究就会发现&#xff0c;版本号不是以固定值递增的&#xff0c;有时候第三位加 1&#xff0c;有时候加 2&#xff0c;有时候直接把第一位加 1&…

【Redis】String的介绍与应用详解

大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#xff0c;但是也想日更的人。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4aa;&#x1f4aa;&#x1f4aa…

操作系统总结(2)

目录 2.1 进程的概念、组成、特征 &#xff08;1&#xff09;知识总览 &#xff08;2&#xff09;进程的概念 &#xff08;3&#xff09;进程的组成—PCB &#xff08;4&#xff09;进程的组成---程序段和数据段 &#xff08;5&#xff09;程序是如何运行的呢&#xff1f…

《中国企业报》集团数字产业发展研究院介绍

《中国企业报》集团数字产业发展研究院&#xff08;以下简称“中企数研院”&#xff09;&#xff0c;隶属于《中国企业报》集团管理。“中企数研院”致力于“数字经济产业化发展战略”大背景下&#xff0c;以“县域数字经济”、“企业数字化转型”及“数字人民币”推广等发展方…