Linux56:读取人脸图片并把特征值保存到sqlite3数据库

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);

缺点:

  1. 占用空间大:6MB vs 100KB(60倍差距)
  2. 无法还原:丢失了原始JPEG格式,无法再保存为JPEG文件
  3. 浪费带宽:数据库读写慢
  4. **格式固定:**只能作为原始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. 特征提取(稳定、可靠)

识别结果 ✅

相关推荐
刘~浪地球1 小时前
MongoDB分片集群实战:水平扩展海量数据
数据库·mongodb
鸽芷咕1 小时前
KingbaseES中的PL_SQL编程:存储过程、函数、触发器与包的开发指南
数据库·sql·oracle
4311媒体网1 小时前
帝国CMS新手入门教程:从零开始掌握企业级建站系统
数据库
韩小兔修媛史1 小时前
Redis面试八股文总结
数据库·redis·面试
小则又沐风a1 小时前
Linux下的Git的上传(版本控制器)
linux·数据库·git
赵渝强老师1 小时前
【赵渝强老师】PostgreSQL的数据预热扩展pg_prewarm
数据库·postgresql
小新同学^O^2 小时前
简单学习 --> 数据加密
java·数据库·学习·数据加密
Elastic 中国社区官方博客2 小时前
将 Logstash Pipeline 从 Azure Event Hubs 迁移到 OTel Collector Kafka Receiver
大数据·数据库·人工智能·分布式·elasticsearch·搜索引擎·kafka
Elastic 中国社区官方博客2 小时前
使用 Elasticsearch 与 Kibana 中的 PromQL 调查 Kubernetes 基础设施问题
大数据·数据库·elasticsearch·搜索引擎·信息可视化·kubernetes·全文检索