1.本章节介绍
本章节主要介绍如何通过rockx读取人脸图像并读取人脸特征值保存到sqlite3 数据库里面,在真正的开发中人脸特征值都会存储到数据库里面,一般的数据库有sqlite3 、 mysql等等。(关于数据库的内容,本项目不做一个详细的介绍,这边会带着大家做一个比较简单的数据库的增删改查操作)。
2.rockx 读取人脸特征值保存数据库大体框图

上图是读取人脸特征和图片数据写到数据库的流程。
第一步:要连接和读取人脸的sqlite3数据库。
第二步:创建三个rockx句柄,包括人脸检测句柄(face_det_handle)、人脸识别句柄(face_recognize_handle)、人脸关键点句柄(face_5landmarks_handle);
第三步:使用rockx_image_read读取人脸图片;
第四步:使用FILE的API来获取图片的长度和二进制的图片数据;
第五步:使用rockx_face_align对齐人脸图片;
第六步:使用rockx_face_recognize提取人脸的特征值;
第七步:把人脸特征值和图片二进制数据保存到sqlite3数据库。
3.rockx读取人脸特征值保存数据库的代码截图
3.1.Connection_sqlite3DataBase 连接数据库

这里封装了一个了SQLITE3连接数据库的函数Connection_sqlite3Database,这个函数的实现如下图

这个函数直接调用了sqlite3的api sqlite3_open来初始化人脸数据库,若返回值不等于SQLITE_OK则初始化数据库失败,否则就初始化成功。
3.2. 创建三个 rockx 句柄

这里需要通过rockx_create创建三个句柄来完成人脸检测和人脸识别,分别是人脸检测句柄(ROCKX_MODULE_FACE_DETECTION_V2 )、人脸识别句柄(ROCKX_MODULE_FACE_RECOGNIZE )、人脸关键点检测句柄(ROCKX_MODULE_FACE_LANDMARK_5)。
3.3. 读取人脸图片

上图是读取人脸的图片,这里使用的是rockx_image_read来读取人脸图片,其中input_image是需要从命令行输入的。
3.4. 使用 FILE 读取图片的二进制数据和长度

上面的代码是读取图片的数据和长度,这里直接使用的是系统的API。通过ftell来获取图片的长度,并使用fread来读取图片的二进制数据。
3.5. 使用 rockx_face_detect 检测人脸数据并找到最精确的人脸

使用rockx_face_detect检测图片的人脸数据,并找到最精确的人脸。具体的查找过程在get_max_face里面,我们来看看这个的实现。

get_max_face 的实现是循环人脸的数量,并计算当前人脸的面积(cur_face_box_area)和最大人脸面积(max_face_box_area)。如果当前人脸的面积(cur_face_box_area )>最大人脸面积(max_face_box_area),则把当前人脸面积赋值给最大人脸面积,max_face就是最精确的人脸数据了。
3.6. 对齐人脸数据并且提取人脸特征值

找到最精确的人脸数据后,就需要把人脸进行对齐,对齐的API是rockx_face_align 。 对齐了人脸数据后,就使用rockx_face_recognize来识别图片的人脸数据并提取特征值。
3.7. 把人脸特征值和图片数据保存到 sqlite3 数据库


(图二)
最后一步就是把人脸特征值和图片数据保存到sqlite3数据库,要保存数据库要用到sql语句。这里用到的sql语句是
"insert into face_data_table(name,face_feature,feature_size,image_data,image_size) values (?,?,?,?,?);"
insert语句是经典的sql添加数据的语句, 这其中"face_data_table "是我们需要查询的人脸数据表格,它包含了name(人脸名称)、face_feature(人脸特征值)、feature_size(特征值长度)、image_data(图片数据)、image_size(图片长度)(人脸表格数据如图二)。
sqlite3_prepare函数将SQL语句编译成可执行的字节码,从而进行各种SQL的操作,包括: 查询、插入、更新、 删除。
sqlite3_bind_text(stmt, 1, name, strlen(name), NULL) **:**将name绑定到该位置,我们可以执行该预处理语句以将name插入到数据库中。
sqlite3_bind_blob(stmt, 2, feature, featureSize, NULL) : 将feature绑定到该位置,我们可以执行该预处理语句以将人脸特征值(feature)插入到数据库中。
sqlite3_bind_int(stmt, 3, featureSize) : 将feature_size绑定到该位置,我们可以执行该预处理语句以将人脸特征值长度(feature_size)插入到数据库中。
sqlite3_bind_blob(stmt, 4, image_data, image_length, NULL) : 将image_data 绑定到该位置,我们可以执行该预处理语句以将图片数据(image_data)插入到数据库中。
sqlite3_bind_int(stmt, 5, image_length) : 将image_size 绑定到该位置,我们可以执行该预处理语句以将图片长度(image_size)插入到数据库中。
4.在板子上查询数据库的人脸数据
4.1. 打开 sqlite3 数据库

使用sqlite3 face.db打开人脸数据库
4.2. 查询人脸的数据

使用SQL的查询语句查询人脸识别数据,使用的sql语句是select * from face_data_table,查询的结果就是上面截图。
CREATE TABLE face_data_table (id int (20),name varchar (20),face_feature blob,feature_size int (20),image_data blob,image_size int(20) );
5.代码详解
main()
├── Connection_sqlite3DataBase() // 打开数据库连接
├── rockx_create() × 3 // 创建AI推理句柄
├── rockx_image_read() // 读取图片到内存
├── run_face_recognize() // 核心处理函数
│ ├── rockx_face_detect() // 检测人脸
│ ├── get_max_face() // 选择最大人脸
│ ├── rockx_face_align() // 人脸对齐
│ ├── rockx_face_recognize() // 提取特征
│ └── insert_face_data_toDataBase() // 存入数据库
└── return 0
第一部分:main函数 - 参数检查与数据库连接
1.1 参数检查
cs
if (argc != 3)
{
printf("Usage: Insert DataBase ./sqlite3_operation_test name image_path\n");
return -1;
}
| 项目 | 说明 |
|---|---|
| argc | 命令行参数个数,包含程序名本身 |
| argv[0] | 程序名,如"./sqlite3_operation_test" |
| argv[1] | 第一个参数,姓名,如"harry" |
| argv[2] | 第二个参数,图片路径,如"/sdcard/face.jpg" |
| 功能 | 确保用户输入了姓名和图片路径,否则打印用法并退出 |
1.2 连接数据库
cs
printf("Start Connection sqlite3......................\n");
Connection_sqlite3DataBase();//打开SQLite3数据库文件
printf("End Connection sqlite3DataBase......................\n");

|---------|----------------------------------------------------------|
| 原型 | int sqlite3_open(const char *filename, sqlite3 **ppDb) |
| 参数1 | 数据库文件路径,"/userdata/face.db" |
| 参数2 | 输出参数,返回数据库句柄,存入全局变量db |
| 返回值 | SQLITE_OK(0)-成功;其他-失败 |
| 功能 | 打开或创建SQLite3数据库文件 |
|---------|-------------------------------------------|
| 原型 | const char *sqlite3_errmsg(sqlite3 *db) |
| 参数 | 数据库句柄 |
| 返回值 | 错误信息字符串 |
| 功能 | 获取最后一次数据库操作的错误描述 |
|---------|----------------------------------|
| 原型 | int sqlite3_close(sqlite3 *db) |
| 参数 | 要关闭的数据库句柄 |
| 返回值 | SQLITE_OK-成功 |
| 功能 | 关闭数据库连接,释放资源 |
关于stderr和fprintf
C语言默认打开的3个标准流:
| 流名称 | 文件描述符 | 默认目标 | 用途 |
|---|---|---|---|
| stdin | 0 | 键盘 | 标准输入 |
| stdout | 1 | 屏幕 | 标准输出(普通信息) |
| stderr | 2 | 屏幕 | 标准错误(错误信息) |
int fprintf(FILE *stream, const char *format, ...)
| 项目 | 说明 |
|---|---|
| 参数1 stream | 输出目标文件流(stderr/stdout/文件指针) |
| 参数2 format | 格式化字符串 |
| 参数3 ... | 可变参数,要输出的数据 |
| 返回值 | 成功返回输出的字符数,失败返回负数 |
| 项目 | 说明 |
|---|---|
| 全称 | Standard Error(标准错误输出) |
| 文件描述符 | 2 |
| 默认目标 | 终端/控制台屏幕 |
| 特点 | 无缓冲,立即输出 |
特点:
- 立即显示,不需要刷新缓冲区
- 即使程序后面崩溃,这条信息也已经输出
- 不会受到输出重定向的影响(见下文)
stderr vs stdout 的重要区别
cs
# 1. 正常运行 - 两者都显示在屏幕
./program
# stdout: "Normal message" ← 显示
# stderr: "Error message" ← 显示
# 2. 重定向标准输出到文件
./program > output.txt
# stdout: "Normal message" ← 写入 output.txt
# stderr: "Error message" ← 仍然显示在屏幕
# 3. 重定向标准输出和标准错误
./program > output.txt 2>&1
# stdout: "Normal message" ← 写入 output.txt
# stderr: "Error message" ← 也写入 output.txt
# 4. 只重定向标准错误
./program 2> error.txt
# stdout: "Normal message" ← 显示在屏幕
# stderr: "Error message" ← 写入 error.txt
第二部分:main函数 - RockX引擎初始化
cs
rockx_config_t *config = rockx_create_config();
rockx_add_config(config, ROCKX_CONFIG_DATA_PATH, "/userdata/rockx_data/");
2.1 rockx_create_config()
|---------|---------------------------------------------|
| 原型 | rockx_config_t *rockx_create_config(void) |
| 参数 | 无 |
| 返回值 | 成功返回配置句柄指针,失败返回NULL |
| 功能 | 创建RockX引擎配置对象,用于设置算法模型路径等参数 |
2.2 rockx_add_config()
|----------------|---------------------------------------------------------------------------------------------|
| 原型 | int rockx_add_config(rockx_config_t *config, rockx_config_type_t type, const char *value) |
| 参数1 config | rockx_create_config返回的配置句柄 |
| 参数2 type | ROCKX_CONFIG_DATA_PATH - 指定模型数据路径 |
| 参数3 value | "/userdata/rockx_data/" - 模型文件存放目录 |
| 返回值 | ROCKX_RET_SUCCESS(0)-成功,其他-失败 |
| 功能 | 向配置对象添加配置项,指定RockX模型文件路径 |
2.3 创建人脸检测、人脸识别、人脸5点关键点检测句柄
cs
// create a face detection handle
ret = rockx_create(&face_det_handle, ROCKX_MODULE_FACE_DETECTION_V2, config, 0);
if (ret != ROCKX_RET_SUCCESS)
{
printf("init rockx module ROCKX_MODULE_FACE_DETECTION error %d\n", ret);
return -1;
}
// create a face landmark handle
ret = rockx_create(&face_5landmarks_handle, ROCKX_MODULE_FACE_LANDMARK_5,
config, 0);
if (ret != ROCKX_RET_SUCCESS)
{
printf("init rockx module ROCKX_MODULE_FACE_LANDMARK_68 error %d\n", ret);
return -1;
}
// create a face recognize handle
ret = rockx_create(&face_recognize_handle, ROCKX_MODULE_FACE_RECOGNIZE,
config, 0);
if (ret != ROCKX_RET_SUCCESS)
{
printf("init rockx module ROCKX_MODULE_FACE_RECOGNIZE error %d\n", ret);
return -1;
}
|-----------------|--------------------------------------------------------------------------------------------------------|
| 原型 | int rockx_create(rockx_handle_t *handle, rockx_module_t module, rockx_config_t *config, int timeout) |
| 参数1 handle | 输出参数,返回创建的算法句柄 |
| 参数2 module | 模块类型,决定使用哪个算法 |
| 参数3 config | 配置对象 |
| 参数4 timeout | 超时时间(毫秒),0表示不超时 |
| 返回值 | ROCKX_RET_SUCCESS(0)-成功,其他-失败 |
第三部分:main函数 - 读取图片
3.1 解码图片为RockX格式
cs
rockx_image_t input_image1;
rockx_image_read(image_path, &input_image1, 1);
|--------------------------|--------------------------------------------------------------------------------------|
| 原型 | int rockx_image_read(const char *path, rockx_image_t *image, int reverse_channels) |
| 参数1 path | 图片文件路径,如"/sdcard/face.jpg" |
| 参数2 image | 输出参数,返回解码后的图像数据 |
| 参数3 reverse_channels | 1-交换R和B通道(RGB→BGR),0-不交换 |
| 返回值 | ROCKX_RET_SUCCESS-成功,其他-失败 |
typedef struct {
unsigned char *data; // 图像像素数据指针
int width; // 图像宽度(像素)
int height; // 图像高度(像素)
int pixel_format; // 像素格式,如ROCKX_PIXEL_RGB24
int data_size; // 数据总大小(字节)
} rockx_image_t;
3.2 读取原始图片二进制数据
cs
FILE *imageFile = fopen(image_path, "rb");
|---------|-------------------------------------------------------|
| 原型 | FILE *fopen(const char *filename, const char *mode) |
| 参数1 | 文件路径 |
| 参数2 | "rb" - 以二进制读模式打开 |
| 返回值 | 成功返回FILE指针,失败返回NULL |
| 功能 | 打开图片文件,准备读取原始数据 |
cs
fseek(imageFile, 0L, SEEK_END);
long fileSize = ftell(imageFile);
rewind(imageFile);
|----------------|----------------------------------------------------|
| 原型 | int fseek(FILE *stream, long offset, int origin) |
| 参数1 | 文件指针 |
| 参数2 offset | 0L - 偏移0字节 |
| 参数3 origin | SEEK_END - 从文件末尾开始偏移 |
| 返回值 | 0-成功,非0-失败 |
| 功能 | 移动文件指针到末尾 |
|---------|------------------------------|
| 原型 | long ftell(FILE *stream) |
| 参数 | 文件指针 |
| 返回值 | 当前文件指针位置(字节数) |
| 功能 | 获取当前文件指针位置,即文件大小 fileSize |
|---------|-----------------------------|
| 原型 | void rewind(FILE *stream) |
| 参数 | 文件指针 |
| 返回值 | 无 |
| 功能 | 将文件指针重置到文件开头 |
cs
unsigned char *buffer = new unsigned char[fileSize];
size_t bytesRead = fread(buffer, sizeof(unsigned char), fileSize, imageFile);
|----------------|--------------------------------------------------------------------|
| 原型 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) |
| 参数1 ptr | 存储读取数据的缓冲区 |
| 参数2 size | 每个元素的大小(sizeof(unsigned char)=1) |
| 参数3 nmemb | 要读取的元素个数(fileSize) |
| 参数4 stream | 文件指针 |
| 返回值 | 实际读取的元素个数 |
| 功能 | 从文件读取二进制数据到缓冲区 |
详细扩展看1。
第四部分:run_face_recognize核心函数
cs
int run_face_recognize(const char *name, rockx_image_t *in_image,
rockx_face_feature_t *out_feature,
unsigned char *buffer, int buffer_size)
|---------------------|---------------|
| 参数1 name | 人员姓名 |
| 参数2 in_image | RockX格式的输入图像 |
| 参数3 out_feature | 输出参数,返回512维特征 |
| 参数4 buffer | 原始图片二进制数据 |
| 参数5 buffer_size | 原始图片数据大小 |
| 返回值 | 0-成功,-1-失败 |
4.1 人脸检测
cs
rockx_object_array_t face_array;
memset(&face_array, 0, sizeof(rockx_object_array_t));
ret = rockx_face_detect(face_det_handle, in_image, &face_array, NULL);
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| 原型 | int rockx_face_detect(rockx_handle_t handle, rockx_image_t *image, rockx_object_array_t *objects, rockx_face_detection_config_t *config) |
| 参数1 handle | 人脸检测句柄(ROCKX_MODULE_FACE_DETECTION_V2) |
| 参数2 image | 输入图像 |
| 参数3 objects | 输出参数,返回检测到的人脸数组 |
| 参数4 config | 检测配置,传NULL使用默认值 |
| 返回值 | ROCKX_RET_SUCCESS-成功,其他-失败 |
typedef struct {
rockx_object_t *object; // 人脸对象数组指针
int count; // 检测到的人脸数量
} rockx_object_array_t;
typedef struct {
rockx_rect_t box; // 人脸框 {left, top, right, bottom}
float score; // 置信度,范围0~1
int id; // 跟踪ID(多帧跟踪时使用)
} rockx_object_t;
typedef struct {
int left; // 左上角X坐标
int top; // 左上角Y坐标
int right; // 右下角X坐标
int bottom; // 右下角Y坐标
} rockx_rect_t;
最大人脸和人脸矫正可看扩展
4.2 获取最大人脸
cs
rockx_object_t *max_face = get_max_face(&face_array);
rockx_object_t *get_max_face(rockx_object_array_t *face_array)
项目 说明 参数 人脸检测结果数组 返回值 面积最大的人脸对象指针,无人脸返回NULL
函数内部实现:
// 1. 检查是否检测到人脸
if (face_array->count == 0) {
return NULL;
}
// 2. 遍历所有人脸,计算面积
for (i = 0; i < face_array->count; i++) {
rockx_object_t *cur_face = &(face_array->object[i]);
// 计算当前人脸框面积
int cur_face_box_area = (cur_face->box.right - cur_face->box.left) *
(cur_face->box.bottom - cur_face->box.top);
// 比较面积,保留更大的
if (cur_face_box_area > max_face_box_area) {
max_face = cur_face;
}
}
4.3 人脸对齐
cs
rockx_image_t out_img;
memset(&out_img, 0, sizeof(rockx_image_t));
ret = rockx_face_align(face_5landmarks_handle, in_image, &(max_face->box), NULL, &out_img);
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 原型 | int rockx_face_align(rockx_handle_t handle, rockx_image_t *in_image, rockx_rect_t *face_box, rockx_face_landmark_t *landmarks, rockx_image_t *out_image) |
| 参数1 handle | 5点关键点句柄(ROCKX_MODULE_FACE_LANDMARK_5) |
| 参数2 in_image | 输入原始图像 |
| 参数3 face_box | 人脸框位置 |
| 参数4 landmarks | 可选,传NULL则自动检测关键点 |
| 参数5 out_image | 输出参数,返回对齐后的正脸图像 |
| 返回值 | ROCKX_RET_SUCCESS-成功,其他-失败 |

4.4 特征提取
cs
rockx_face_recognize(face_recognize_handle, &out_img, out_feature);
|-----------------|--------------------------------------------------------------------------------------------------------|
| 原型 | int rockx_face_recognize(rockx_handle_t handle, rockx_image_t *image, rockx_face_feature_t *feature) |
| 参数1 handle | 人脸识别句柄(ROCKX_MODULE_FACE_RECOGNIZE) |
| 参数2 image | 对齐后的人脸图像(通常是112x112) |
| 参数3 feature | 输出参数,返回512维特征向量 |
| 返回值 | ROCKX_RET_SUCCESS-成功,其他-失败 |
typedef struct {
float feature[512]; // 512维浮点特征向量
int len; // 特征长度,固定为512
} rockx_face_feature_t;
特征向量说明:
- 每个特征值范围约 -2.0 ~ +2.0
- 同一个人不同照片的特征向量相似度>0.6
- 不同人的特征向量相似度<0.3
4.5 插入数据库
cs
insert_face_data_toDataBase(name, out_feature->feature, FEATURE_SIZE, buffer, buffer_size);
void insert_face_data_toDataBase(
const char *name,
float feature[512],
int featureSize,
uint8_t *image_data,
int image_size)
项目 说明 参数1 name 人员姓名 参数2 feature 512维特征数组 参数3 featureSize 512,特征维度数 参数4 image_data 原始图片二进制数据 参数5 image_size 图片数据大小(字节)
SQLite3相关函数详解:
cs
sqlite3_prepare(db, "insert into face_data_table(name,face_feature,feature_size,image_data,image_size) values (?,?,?,?,?);", -1, &stmt, NULL);
sqlite3_prepare() 详细说明:
|----------------|-------------------------------------------------------------------------------------------------------------|
| 原型 | int sqlite3_prepare(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail) |
| 参数1 db | 数据库句柄 |
| 参数2 zSql | SQL语句字符串 |
| 参数3 nByte | SQL长度,-1表示自动计算 |
| 参数4 ppStmt | 输出参数,返回预处理语句对象 |
| 参数5 pzTail | 未使用部分,可传NULL |
| 返回值 | SQLITE_OK-成功,其他-失败 |
| 功能 | 将SQL语句编译为字节码程序 |
SQL语句中的?说明:
cs
INSERT INTO face_data_table(name, face_feature, feature_size, image_data, image_size)
VALUES (?, ?, ?, ?, ?);
- 第1个? → name
- 第2个? → face_feature (特征数据)
- 第3个? → feature_size (特征维度)
- 第4个? → image_data (图片二进制数据)
- 第5个? → image_size (图片大小)
cs
sqlite3_bind_text(stmt, 1, name, strlen(name), NULL);
sqlite3_bind_blob(stmt, 2, feature, featureSize, NULL);
sqlite3_bind_int(stmt, 3, featureSize);
sqlite3_bind_blob(stmt, 4, image_data, image_size, NULL);
sqlite3_bind_int(stmt, 5, image_size);
|--------------------|-----------------------------------------------------------------------------------------------------------|
| 原型 | int sqlite3_bind_text(sqlite3_stmt *stmt, int idx, const char *val, int len, void (*destructor)(void*)) |
| 参数1 stmt | 预处理语句对象 |
| 参数2 idx | 参数序号(从1开始) |
| 参数3 val | 文本值 |
| 参数4 len | 文本长度,-1自动计算 |
| 参数5 destructor | 析构函数,NULL表示不自动释放 |
| 返回值 | SQLITE_OK-成功 |
| 功能 | 绑定字符串参数到SQL语句的?占位符 |
|--------------------|-----------------------------------------------------------------------------------------------------------|
| 原型 | int sqlite3_bind_blob(sqlite3_stmt *stmt, int idx, const void *val, int len, void (*destructor)(void*)) |
| 参数1 stmt | 预处理语句对象 |
| 参数2 idx | 参数序号(从1开始) |
| 参数3 val | 二进制数据指针 |
| 参数4 len | 数据长度(字节) |
| 参数5 destructor | 析构函数,NULL表示不自动释放 |
| 返回值 | SQLITE_OK-成功 |
| 功能 | 绑定二进制数据(BLOB)到SQL语句 |
注意:featureSize * sizeof(float) = 512 × 4 = 2048字节
|--------------|--------------------------------------------------------------|
| 原型 | int sqlite3_bind_int(sqlite3_stmt *stmt, int idx, int val) |
| 参数1 stmt | 预处理语句对象 |
| 参数2 idx | 参数序号(从1开始) |
| 参数3 val | 整数值 |
| 返回值 | SQLITE_OK-成功 |
| 功能 | 绑定整型参数到SQL语句 |
|---------|----------------------------------------|
| 原型 | int sqlite3_step(sqlite3_stmt *stmt) |
| 参数 | 预处理语句对象 |
| 返回值 | SQLITE_DONE-执行完成,SQLITE_ROW-查询到一行数据 |
| 功能 | 执行预处理语句 |
4.6 释放资源
cs
rockx_image_release(&out_img);
|---------|-------------------------------------------------|
| 原型 | int rockx_image_release(rockx_image_t *image) |
| 参数 | 要释放的rockx_image_t对象 |
| 返回值 | ROCKX_RET_SUCCESS-成功 |
| 功能 | 释放rockx_image_t内部动态分配的data内存 |
第五部分:完整代码执行流程图
cs
┌─────────────────────────────────────────────────────────────────────┐
│ main函数开始 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 步骤1 : 参数检查 │
│ if (argc != 3) → 打印用法并退出 │
│ argv[1] = 姓名, argv[2] = 图片路径 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 步骤2 : 连接数据库 │
│ Connection_sqlite3DataBase() │
│ └─ sqlite3_open("/userdata/face.db", &db) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 步骤3 : 初始化RockX引擎 │
│ config = rockx_create_config() │
│ rockx_add_config(config, ROCKX_CONFIG_DATA_PATH, "/userdata/...") │
│ rockx_create(&face_det_handle, ROCKX_MODULE_FACE_DETECTION_V2, ...) │
│ rockx_create(&face_5landmarks_handle, ROCKX_MODULE_FACE_LANDMARK_5) │
│ rockx_create(&face_recognize_handle, ROCKX_MODULE_FACE_RECOGNIZE) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 步骤4 : 读取图片 │
│ rockx_image_read(image_path, &input_image1, 1) → 解码为RockX格式 │
│ fopen(image_path, "rb") → 打开原始文件 │
│ fseek / ftell / rewind → 获取文件大小 │
│ fread(buffer, 1, fileSize, imageFile) → 读取二进制数据 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 步骤5 : 人脸识别处理 run_face_recognize() │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 5.1 人脸检测 rockx_face_detect() │ │
│ │ → 返回所有人脸的位置和置信度 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 5.2 获取最大人脸 get_max_face() │ │
│ │ → 遍历所有检测框,按面积排序,返回最大的 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 5.3 人脸对齐 rockx_face_align() │ │
│ │ → 检测5个关键点(双眼、鼻尖、嘴角) │ │
│ │ → 仿射变换,将人脸转为标准正面照 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 5.4 特征提取 rockx_face_recognize() │ │
│ │ → 将对齐后的人脸转换为512维特征向量 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 5.5 插入数据库 insert_face_data_toDataBase() │ │
│ │ → sqlite3_prepare() 准备SQL语句 │ │
│ │ → sqlite3_bind_text() 绑定姓名 │ │
│ │ → sqlite3_bind_blob() 绑定特征向量(2048字节) │ │
│ │ → sqlite3_bind_int() 绑定特征维度(512) │ │
│ │ → sqlite3_bind_blob() 绑定原始图片数据 │ │
│ │ → sqlite3_bind_int() 绑定图片大小 │ │
│ │ → sqlite3_step() 执行插入 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 步骤6 : 释放资源 │
│ rockx_image_release(&out_img) → 释放对齐后的图像内存 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ main函数结束 │
│ return 0; │
│ │
└─────────────────────────────────────────────────────────────────────┘
第六部分:数据库表结构说明
CREATE TABLE face_data_table (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 自增主键(未显式使用)
name TEXT, -- 姓名
face_feature BLOB, -- 512维特征向量(2048字节)
feature_size INTEGER, -- 特征维度(512)
image_data BLOB, -- 原始图片二进制数据
image_size INTEGER -- 图片大小(字节)
);
扩展
1.两次读取同一个人脸图片
cs
// 第一次读取:解码为RockX格式(用于算法处理)
rockx_image_read(image_path, &input_image1, 1);
// 第二次读取:原始二进制数据(用于存储到数据库)
FILE *imageFile = fopen(image_path, "rb");
// ... 读取到buffer
1.两种读取方式的本质区别
| 对比项 | 第一次读取 | 第二次读取 |
|---|---|---|
| 函数 | rockx_image_read() |
fopen() + fread() |
| 结果 | 解码后的像素数据 | 原始文件字节流 |
| 格式 | RGB/BGR像素数组 | JPEG/PNG压缩格式 |
| 大小 | 很大(宽×高×3字节) | 较小(压缩后) |
| 用途 | 人脸识别算法处理 | 存储到数据库备份 |
2.详细对比分析
第一次读取:rockx_image_read()
cs
rockx_image_t input_image1;
rockx_image_read(image_path, &input_image1, 1);
内部做的事情:

input_image1 结构内容:
cs
input_image1 = {
.data = 0xffff1234, // 指向解码后的RGB像素数组
.width = 1920, // 图像宽度
.height = 1080, // 图像高度
.pixel_format = ROCKX_PIXEL_RGB24, // RGB格式
.data_size = 1920*1080*3 // 约6MB
};
第二次读取:原始文件
cs
FILE *imageFile = fopen(image_path, "rb");
fread(buffer, 1, fileSize, imageFile);
内部做的事情:

buffer内容: 保持JPEG压缩格式的原始字节
3、为什么不能只读一次?
如果只用 rockx_image_read()
cs
rockx_image_read(image_path, &input_image1, 1);
// 问题:input_image1.data 是解码后的像素数据
// 存储到数据库:
insert_face_data_toDataBase(name, feature, 512,
input_image1.data, // ← 像素数据,约6MB!
input_image1.data_size);
缺点:
- 占用空间大:6MB vs 100KB(60倍差距)
- 无法还原:丢失了原始JPEG格式,无法再保存为JPEG文件
- 浪费带宽:数据库读写慢
- **格式固定:**只能作为原始RGB数据使用
如果只用 fopen() + fread()
cs
fread(buffer, 1, fileSize, imageFile);
// 问题:buffer是压缩的JPEG数据
// 人脸识别需要解码后的像素数据
rockx_face_detect(handle, buffer, ...); // ❌ 错误!需要rockx_image_t格式
缺点:
- RockX算法需要解码后的像素数据,不能直接处理JPEG压缩数据
4、完整的数据流向图
cs
图片文件 (100KB JPEG)
│
│
┌──────────────┴──────────────┐
│ │
▼ ▼
rockx_image_read() fopen() + fread()
│ │
▼ ▼
解码为像素数据 保持原始JPEG格式
(input_image1) (buffer)
│ │
│ │
▼ │
人脸检测/对齐/识别 │
│ │
▼ │
提取512维特征 │
│ │
└──────────┬──────────────────┘
│
▼
insert_face_data_toDataBase()
│
▼
┌─────────┐
│ 数据库 │
├─────────┤
│ name │ ← "张三"
│ feature │ ← 512维特征
│ image │ ← JPEG原始数据 (100KB) ✅
└─────────┘
当前方案(两次读取)的优点
cs
// 存储到数据库的是压缩后的原始文件
sqlite3_bind_blob(stmt, 4, image_data, image_size, NULL);
// image_data 是JPEG格式,只有100KB左右
|----------|----------------------|
| 节省空间 | 100KB vs 6MB,节省60倍空间 |
| 可还原 | 可以导出为新的JPEG文件 |
| 快速读取 | 读取100KB比6MB快得多 |
| 标准格式 | 任何图片查看器都能打开 |
2.最大人脸和人脸矫正
1.最大人脸

摄像头可能拍到多张人脸:
| 人脸 | 大小(像素) | 距离 | 清晰度 | 是否为主角 |
|---|---|---|---|---|
| 主角A | 200×200 | 1米 | 清晰 | ✅ 是 |
| 背景B | 50×50 | 5米 | 模糊 | ❌ 否 |
| 边缘C | 30×30 | 8米 | 很模糊 | ❌ 否 |
最大人脸可靠:
| 优势 | 说明 |
|---|---|
| 特征更丰富 | 大脸包含更多像素点,纹理细节更清晰 |
| 识别准确率高 | 更多特征点可供提取,识别率提高30%+ |
| 抗干扰性强 | 不易受光线、角度影响 |
| 最可能是目标 | 距离近、正对镜头的人通常是主要识别对象 |
2.人脸对齐
原始人脸可能各种姿态:

人脸对齐的作用

对比实验数据
| 人脸姿态 | 未矫正识别率 | 矫正后识别率 | 提升 |
|---|---|---|---|
| 正面 | 95% | 96% | +1% |
| 歪头15° | 70% | 93% | +23% |
| 歪头30° | 45% | 88% | +43% |
| 侧脸45° | 25% | 75% | +50% |
| 仰头20° | 60% | 90% | +30% |
为什么用5点:
5点的优势:
-
计算快(嵌入式设备友好)
-
足够计算仿射变换(理论上3个点就够了)
-
稳定性好(不易受表情影响)
5点的位置选择逻辑:
- 眼睛 ×2: 决定人脸的水平方向和旋转角度
- 鼻子 ×1: 决定垂直中心线
- 嘴角 ×2: 决定下半脸的对称性
这5个点形成一个稳定的几何结构!
异常情况处理
情况1:没有检测到人脸
可能原因:
- 图片中真的没有人脸
- 人脸太小(小于32×32)
- 光线太暗
- 人脸被遮挡
情况2:人脸对齐失败
可能原因:
- 人脸框检测有误差
- 5点关键点检测失败(如闭眼、遮挡)
- 图像质量太差
完整的人脸识别标准化流程
原始图像
│
├─→ 1. 人脸检测(找出所有人脸)
│
├─→ 2. 选择最大人脸(排除干扰)
│
├─→ 3. 5点关键点检测(定位特征)
│
├─→ 4. 仿射变换矫正(标准化姿态)
│
└─→ 5. 特征提取(稳定、可靠)
│
▼
识别结果 ✅