Linux57:读取人脸数据库并保存到map
1.本章节介绍本章节主要介绍如何通过查询人脸数据库数据库表格把数据存储到map容器map容器底层是内存处理用map去处理数据能够达到高效快速查询的效果。在这个项目里面map起到了快速查询的功能key存储的是人脸的结构体(People)value存储的是人脸具体的数据(rockx_face_feature_t)。2.读取数据库保存到map流程框图上图是读取数据库并加载到Map的过程分别有三步第一步加载并连接sqlite3数据库、第二步通过select查询人脸表格的数据、第三步把表格的数据循环插入到map容器。3.读取数据库保存到map的代码截图init_face_data是读取数据库数据并插入到map容器的自定义函数这里有几个重要的步骤分别是加载数据库(Connection_sqlite3DataBase)、查询数据库信息并存放到Map容器(QueryPeopleData)、把Map存放到全局变量里面(set_thread_map)3.1.加载并连接sqlite3数据库这里封装了一个了SQLITE3连接数据库的函数Connection_sqlite3Database这个函数的实现如下图这个函数直接调用了sqlite3的api sqlite3_open来初始化人脸数据库若返回值不等于SQLITE_OK则初始化数据库失败否则就初始化成功。3.2.通过select查询人脸表格的数据上图是通过sql语句查询人脸表格”select name, feature_size, face_feature, image_size, image_data from face_data_table”, 其中name: 名字、feature_size特征值长度、face_feature:人脸特征数据、image_size图片长度、image_data图片数据。face_data_table则是存储人脸的数据库表格。并使用sqlite3_prepare进行sql的预处理。3.3.读取查询的结果并循环插入map容器上图是读取sqlite3数据库的数据并循环插入到map容器。这里的关键是使用了sqlite3提供的函数sqlite3_step来获取select的结果若结果等于SQLITE_ROW(表示当前返回结果有数据)则表示有数据就获取每一行的数据库数据。sqlite3_column_text(stmt, 0)表示的是查找select语句的第一个元素name索引号是0类型是字符串并进行绑定。sqlite3_column_int(stmt, 1)表示的是查找select语句的第二个元素feature_size索引号是1类型是int, 并进行绑定。sqlite3_column_blob(stmt, 2)表示的是查找select语句的第三个元素face_feature索引号是2类型是blob, 并进行绑定。sqlite3_column_int(stmt, 3)表示的是查找select语句的第四个元素image_size索引号是3类型是int, 并进行绑定。sqlite3_column_blob(stmt, 4)表示的是查找select语句的第五个元素image_data索引号是4类型是blob, 并进行绑定。读取完上述的数据后就把上述的数据插到map。4.代码详解1.连接数据库int Connection_sqlite3DataBase()功能打开SQLite3数据库连接参数无返回值0-成功失败则exit(1)退出程序2.定义 task_idint task_id 0;定义 task_id标识当前任务或线程的ID编号3.创建 S_THREAD_MAP 对象S_THREAD_MAP thread_map;class S_THREAD_MAP {public:int map_id;mapstring, rockx_face_feature_t thread_map;mapPeople, rockx_face_feature_t thread_people_map;};map_id类型int访问权限public公开用途存储这个map对象的ID编号当前值未初始化垃圾值thread_map类型mapstring, rockx_face_feature_t访问权限public用途存储 姓名→人脸特征 的映射表当前状态空maptypedef struct {float feature[512]; // 512维特征向量int len; // 特征长度固定512} rockx_face_feature_t;map 容器说明键keystring类型存储姓名值valuerockx_face_feature_t类型存储512维特征向量特点自动按姓名排序thread_people_map类型mapPeople, rockx_face_feature_t访问权限public用途存储 People对象→人脸特征 的映射表当前状态空mapstruct People {string people_name; // 姓名vectorchar images; // 图片二进制数据};thread_map 对象创建后的内存状态thread_map (对象)│├── map_id ? (垃圾值未初始化)│├── thread_map {} (空map)│└── thread_people_map {} (空map)4.查询数据库mapPeople, rockx_face_feature_t maps QueryPeopleData();功能从数据库查询所有人脸完整数据参数无返回值mapPeople, rockx_face_feature_t- People到特征的映射表mapPeople, rockx_face_feature_t QueryPeopleData() { rockx_face_feature_t rockx_face_feature {0, 0}; mapPeople, rockx_face_feature_t people_map; sqlite3_stmt *stmt; char *sql select name, feature_size, face_feature, image_size, image_data from face_data_table; int id 0, len 0; char *name; int feature_size; int image_size; vectorchar images; People first_people; int ret sqlite3_prepare(db, sql, strlen(sql), stmt, 0); if (ret SQLITE_OK) { while (sqlite3_step(stmt) SQLITE_ROW) { name (char *)sqlite3_column_text(stmt, 0); printf(name %s\n, name); feature_size sqlite3_column_int(stmt, 1); printf(feature_size %d\n, feature_size); const void *feature sqlite3_column_blob(stmt, 2); memset(rockx_face_feature.feature, 0, feature_size); memcpy(rockx_face_feature.feature, feature, feature_size); rockx_face_feature.len feature_size; image_size sqlite3_column_int(stmt, 3); printf(image_size %d\n, image_size); const char *image_data (const char *)sqlite3_column_blob(stmt, 4); for (int i 0; i image_size; i) { images.push_back(image_data[i]); } first_people.people_name string(name); first_people.images images; string str(name); // people_map.insert(pairconst People, rockx_face_feature_t(first_people, rockx_face_feature)); people_map.insert(make_pair(first_people, rockx_face_feature)); // people_map.insert(first_people, rockx_face_feature); } }1. sqlite3_prepare()sqlite3_stmt *stmt; char *sql select name, feature_size, face_feature, image_size, image_data from face_data_table; int sqlite3_prepare(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail)功能将SQL语句编译成字节码参数1 db数据库句柄参数2 zSqlSQL语句字符串参数3 nByteSQL长度-1表示自动计算参数4 ppStmt输出参数返回预处理语句对象参数5 pzTail未使用部分指针返回值SQLITE_OK(0)表示成功2. sqlite3_step()int sqlite3_step(sqlite3_stmt *pStmt)项目说明功能执行预处理语句移动到下一行结果参数预处理语句句柄返回值SQLITE_ROW - 还有一行数据SQLITE_DONE - 没有更多数据3. sqlite3_column_text()const unsigned char *sqlite3_column_text(sqlite3_stmt *pStmt, int iCol)功能获取当前行的文本列数据参数1预处理语句句柄参数2 iCol列索引0表示第1列name字段返回值const unsigned char*- 指向文本数据的指针4. sqlite3_column_int()int sqlite3_column_int(sqlite3_stmt *pStmt, int iCol)功能获取当前行的整数列数据参数1预处理语句句柄参数2 iCol列索引返回值整数值5. sqlite3_column_blob()const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int iCol)功能获取当前行的二进制数据参数1预处理语句句柄参数2 iCol列索引返回值const void*- 指向二进制数据的指针6. memcpy()void *memcpy(void *dest, const void *src, size_t n)功能拷贝内存区域参数1 dest目标地址参数2 src源地址参数3 n拷贝的字节数返回值目标地址7.vector::push_back()void push_back(const T value)项目说明功能在vector末尾添加一个元素参数要添加的元素值返回值无8.make_pair()pairPeople, rockx_face_feature_t make_pair(People first, rockx_face_feature_t second)项目说明功能创建一对键值对参数1键People对象参数2值特征向量返回值pair对象9. map::insert()pairiterator, bool insert(const value_type val)项目说明功能向map中插入键值对参数pair对象返回值pair迭代器, 是否成功QueryPeopleData() 开始│▼┌─────────────────────────────┐│ sqlite3_prepare() ││ 编译SQL语句 │└─────────────────────────────┘│▼┌─────────────────────────────┐│ while(sqlite3_step()) ││ 遍历每一行数据 │└─────────────────────────────┘│▼┌─────────────────────────────┐│ sqlite3_column_text(stmt,0) ││ 提取数据库信息 │└─────────────────────────────┘│▼┌─────────────────────────────┐│ sqlite3_column_blob(stmt,4) ││ 提取图片数据 ││ for循环 push_back() ││ 拷贝到 vectorchar │└─────────────────────────────┘│▼┌─────────────────────────────┐│ 构建 People 对象 ││ people_name name ││ images 图片数据 │└─────────────────────────────┘│▼┌─────────────────────────────┐│ make_pair() insert() ││ 存入 people_map │└─────────────────────────────┘│▼┌─────────────────────────────┐│ 返回 people_map │└─────────────────────────────┘扩展1.map容器一、查找相关函数函数功能参数返回值find(key)根据键查找元素位置key要查找的键找到返回指向该元素的迭代器没找到返回 end()count(key)统计键出现的次数key要统计的键map中返回0或1因为键不能重复lower_bound(key)查找第一个不小于key的位置key比较的键返回指向第一个 key 的迭代器upper_bound(key)查找第一个大于key的位置key比较的键返回指向第一个 key 的迭代器equal_range(key)获取等于key的范围key要查找的键返回一个pair包含lower_bound和upper_bound二、插入相关函数函数功能参数返回值insert(pair)插入一个键值对pairKey,Value 对象返回pair迭代器,boolbool表示是否插入成功insert(init_list)插入多个元素初始化列表无emplace(args)原地构造并插入构造键值对的参数返回pair迭代器,boolemplace_hint(pos, args)带提示位置的插入pos插入位置提示args构造参数返回迭代器m[key] 100; // 方式1下标最常用m.insert({key, 100}); // 方式2初始化列表m.insert(make_pair(key, 100)); // 方式3make_pairm.insert(pairstring,int(key,100)); // 方式4pair三、删除相关函数函数功能参数返回值erase(key)按键删除元素key要删除的键返回删除的元素个数0或1erase(iterator)按迭代器位置删除iterator指向要删除元素的迭代器返回被删除元素的下一个迭代器erase(first, last)删除范围内的元素first起始位置last结束位置无clear()清空所有元素无无四、容量相关函数函数功能参数返回值size()获取元素个数无map中键值对的数量empty()判断是否为空无true表示空false表示非空max_size()获取最大容量无map能容纳的最大元素个数五、迭代器相关函数函数功能参数返回值begin()获取第一个元素的迭代器无指向第一个元素的迭代器end()获取尾后迭代器无指向最后一个元素之后位置的迭代器rbegin()获取反向第一个元素的迭代器无指向最后一个元素的反向迭代器rend()获取反向尾后迭代器无指向第一个元素之前的反向迭代器cbegin()获取常量正向迭代器无不能修改元素的begin()cend()获取常量尾后迭代器无不能修改元素的end()六、修改相关函数函数功能参数返回值at(key)访问指定键的值带边界检查key要访问的键返回值的引用不存在抛异常operator[]下标访问运算符key要访问的键返回值的引用不存在则自动创建swap(map)交换两个map的内容map要交换的另一个map对象无七、查找操作详解find(key) 是最常用的项目说明功能在map中查找指定的键参数key - 要查找的键值返回值找到 → 返回指向该元素的迭代器可以访问键和值没找到 → 返回 end()尾后迭代器时间复杂度O(log n)count(key) 用于快速判断存在项目说明功能统计指定键出现的次数参数key - 要统计的键返回值0 - 键不存在1 - 键存在时间复杂度O(log n)at(key) 安全的访问方式项目说明功能访问指定键对应的值参数key - 要访问的键返回值键存在 → 返回值的引用键不存在 → 抛出 out_of_range 异常时间复杂度O(log n)operator[] 最方便但有风险项目说明功能访问或创建指定键对应的值参数key - 要访问/创建的键返回值键存在 → 返回已有值的引用键不存在 → 自动创建并返回默认值的引用时间复杂度O(log n)注意即使只读取如果键不存在也会自动创建2.stmt一、stmt 的本质sqlite3_stmt *stmt;项目说明stmt 是什么预处理语句对象Prepared Statement它保存什么SQL语句的编译结果字节码它不保存什么不保存查询结果数据工作原理像是一个迭代器逐行移动获取数据二、实际执行过程详解第1步准备SQL语句sqlite3_prepare(db, sql, strlen(sql), stmt, 0); // 编译SQL语句SELECT name, feature_size, face_feature, image_size, image_dataFROM face_data_table假设数据库中有3条记录行号namefeature_sizeface_featureimage_sizeimage_data1张三512[0.1,0.2,...]102400[0xFF,0xD8,...]2李四512[0.3,0.4,...]98000[0xFF,0xD8,...]3王五512[0.5,0.6,...]105000[0xFF,0xD8,...]此时 stmt 的状态第2步第一次调用 sqlite3_step()while (sqlite3_step(stmt) SQLITE_ROW)sqlite3_step() 函数详解项目说明功能执行预处理语句并移动到下一行结果参数stmt- 预处理语句句柄返回值SQLITE_ROW- 成功移动到一行数据SQLITE_DONE- 没有更多数据SQLITE_ERROR- 执行出错执行过程第1次调用 sqlite3_step(stmt)↓返回 SQLITE_ROW指向第1行数据↓执行循环体内的代码处理第1行↓第2次调用 sqlite3_step(stmt)↓返回 SQLITE_ROW指向第2行数据↓执行循环体内的代码处理第2行↓第3次调用 sqlite3_step(stmt)↓返回 SQLITE_ROW指向第3行数据↓执行循环体内的代码处理第3行↓第4次调用 sqlite3_step(stmt)↓返回 SQLITE_DONE没有第4行了↓退出 while 循环此时stmt 的游标向下移动一行↓┌─────────────────────────────────────────┐│ 第1行: 张三,512,[特征],102400,[图片] ← 现在指向这行 ││ 第2行: 李四,512,[特征],98000,[图片] ││ 第3行: 王五,512,[特征],105000,[图片] │└─────────────────────────────────────────┘stmt 内部保存了当前行的位置但数据还在数据库里没有复制到 stmt 中第3步提取数据1.提取姓名第0列name (char *)sqlite3_column_text(stmt, 0);printf(name %s\n, name);功能获取当前行指定列的文本类型数据参数1stmt- 预处理语句句柄参数20- 列索引0表示第1列name字段返回值const unsigned char*- 指向文本数据的指针name 的类型是 char*需要强制类型转换此时1. SQLite 根据 stmt 保存的位置去数据库找到第1行2. 取出第1行的第0列数据3. 把数据复制到内存中4. 返回指向这个内存的指针stmt 本身仍然不保存数据只是知道去哪里取当前指向第1行name列的值是 张三sqlite3_column_text(stmt, 0) 返回 → 指向 张三 的指针↓name 那个指针↓printf(%s, name) 输出 → 张三2.提取特征大小第1列feature_size sqlite3_column_int(stmt, 1);printf(feature_size %d\n, feature_size);当前指向第1行feature_size列的值是 512sqlite3_column_int(stmt, 1) 返回 → 512↓feature_size 512↓printf 输出 → feature_size 5123.提取特征数据第2列const void *feature sqlite3_column_blob(stmt, 2); //获取数据指针memset(rockx_face_feature.feature, 0, feature_size); //清空目标缓冲区memcpy(rockx_face_feature.feature, feature, feature_size); //拷贝数据rockx_face_feature.len feature_size; //设置长度sqlite3_column_blob() 函数详解项目说明功能获取当前行指定列的二进制数据参数1stmt- 预处理语句句柄参数22- 列索引2表示第3列face_feature字段返回值const void*- 指向二进制数据的指针人脸特征是一个512维的float数组占用 512 × 4 2048 字节是二进制数据不能用文本存储。这里有问题memset(rockx_face_feature.feature, 0, feature_size);memcpy(rockx_face_feature.feature, feature, feature_size);feature_size的值是512特征维度数但实际字节数应该是512 × sizeof(float) 2048正确写法int feature_bytes feature_size * sizeof(float); // 512 * 4 2048memset(rockx_face_feature.feature, 0, feature_bytes);memcpy(rockx_face_feature.feature, feature, feature_bytes);4.提取图片大小第3列image_size sqlite3_column_int(stmt, 3);printf(image_size %d\n, image_size);列索引3 - 第4列image_size字段数据类型INTEGER存储内容图片文件的字节数如 1024005.提取图片数据第4列const char *image_data (const char *)sqlite3_column_blob(stmt, 4);数据库中的image_data字段存储的是原始图片文件的完整内容第4步继续下一行sqlite3_step(stmt); // 移动到第2行此时stmt 的游标移动到第2行┌─────────────────────────────────────────┐│ 第1行: 张三,512,[特征],102400,[图片] ││ 第2行: 李四,512,[特征],98000,[图片] ← 现在指向这行 ││ 第3行: 王五,512,[特征],105000,[图片] │└─────────────────────────────────────────┘之前第1行的数据已经被释放或覆盖了完整数据提取流程图