开放API接口协议和SDK二次开放的人脸识别摄像头
摄像头在中国是非常成熟的产品,整个行业趋于垄断和封闭的状态,要找到一款能方便整合到自己系统的摄像头是非常不容易的事情.
OPCOL是一款AI智能的开放API接口和SDK二次开发的人脸识别摄像头。可以非常轻松的接入的已有的业务系统中。
它有如下特点:
开放API+SDK,API为全HTTP restful风格的极容易集成的接口方式,SDK方式为C/C++插件so动态库扩展方式
完全离线模式,不依赖任何第三方云服务器,数据不会传输到任何第三方服务器,让用户隐私得到保障
设备支持2万张人脸库,人脸识别毫秒级响应,深度学习算法实现人脸检测、跟踪、去重、质量检测。
采用SONY CMOS图像传感器
最大画面尺寸 1080P(1920*1080),最大支持2路视频流和1路JPG图像抓拍通道
不支持判断是否活体检测
API详细说明链接
https://gitee.com/imlsq/pubdoc/blob/master/opcolv300.md
实物图
API介绍
设备提供如下3种途径集成到第三应用
- HTTP协议接口.
- TCP/ip长连接协议对接
- SDK用户自定义研发SO插件集成
TTP方式提供基本的,最简单的对接方式
HTTP请求认证
通过http访问设备的请求需要加上签名安全认证码,以确定合法来源.url格式如下
http://${ip}:${port}/xxx?sign=${xxx}&token=请求令牌,创建一个随机UUID
* | 类型 | 说明 |
ip | string | 设备IP地址(参考内网搜索设备获取ip) |
sign | string | 签名串,token+key组合md5算法之后16进制字符串(32位) key请参考下面如何签名KEY的说明 |
token | string | 请求令牌,客户端每次请求创建一个新的uuid,token不能重复使用 |
签名KEY
用户名和密码登录验证之后会返回md5签名key。
/api/login
method:post Content-Type: application/json
Request body:
{"username":"admin","password":"xxxx"}
* | 类型 | 说明 |
username | string | 用户名固定admin |
password | string | 密码 |
这个url请求无需签名,主机验证用户名和密码之后会返回签名md5 key,后续url请求用这个md5 key进行签名访问
返回结构
{
"code": 20000,
"data": {
"accessKey": "xxxxx"
}
}
* | 类型 | 说明 |
code | string | 20000,设备有处理请求 |
accessKey | string | 签名KEY,后续请求用这个md5 key签名访问 可以把这个key缓存在应用端,设备重置操作KEY会变化 |
修改密码
/api/set_password?old_password=x&new_password=x
method:get
请求参数:
* | 类型 | 说明 |
old_password | string | 密码 |
new_password | string | 新密码 |
设置WIFI
/api/set_wifi
method:post Content-Type: application/json
Request body:
{"ssid":"x","password":"x"}
* | 类型 | 说明 |
ssid | string | wifi ssid |
password | string | wifi password |
Response body:
{
"code": 20000,
"bizCode": 200
}
设备上报接口设置
当设备端有移动侦测或识别到人脸时会通过此接口设置的URL上报。
/api/set_3rd_url?url=http://your_url&key=xxx
method:get
* | 类型 | 说明 |
url | string | 设备数据通过http的 POST提交到此URL,数据格式参考 "人脸识别数据结构说明" |
key | string | 预留 |
人脸识别数据结构说明
{
"event_type": 4,
"time": "1603934998092",
"mac": "76:be:d8:75:29:f0",
"sign": "x",
"track_id": "6",
"score": 86.57,
"person_id": "10010001",
"person_name": "Mr.luo",
"face_base64": "xxxxx",
"origin_base64": "xxxx",
"blur":0.1,
"goodness":0.9
}
* | 类型 | 说明 |
event_type | int | 4 人脸识别事件 |
time | string | 事件发生时间 |
mac | string | 设备mac地址 |
sign | string | 签名串,用于验证是否合法请求源,md5(time+key+mac) 32位16进制字符串 |
track_id | string | 人脸跟踪ID |
score | string | 相似度,分值越高,相似度越高,满分100,通常大于70分判断为相同的人 |
person_id | string | 搜索到的相似度最高的人脸库的人脸ID |
person_name | string | 搜索到的相似度最高的人脸库的人名字 |
face_base64 | string | 抓拍的人脸图像数据(base64编码) |
origin_base64 | string | 抓拍的整个图像数据(base64编码) |
blur | float | 人脸模糊程度属性(0〜1,0代表最清晰,1代表最模糊) |
goodness | float | 人脸品质,根据pose/blur,得到的人脸分数(0〜1,1是最高分,表示人脸好 |
人脸上传
/api/face_upload
method:post
Request body:
人脸照片的base64编码16进制字符串
/9j/4AAQSkZJRgABAQ....
整个请求Body全部为照片的base64编码字符串,不能有其他数据
Response body:
{
"code": 20000,
"bizCode": 200,
"data": {
"id": 896
}
}
* | 类型 | 说明 |
code | int | 20000为上传成功 |
id | int | 人脸保存的唯一ID |
人脸库查询
/api/face_list?page=1&limit=16
method:get
Request body:
* | 类型 | 说明 |
page | int | 分页查询 |
limit | int | 每页记录数量 |
Response body:
{
"code": 20000,
"data": {
"total": 2,
"list": [{
"id": 896,
"file": "/face/896.jpg",
"state": "1"
}, {
"id": 801,
"file": "/face/801.jpg",
"state": "2",
"biz_id": "10010001",
"name": "Mr.luo"
}]
}
}
* | 类型 | 说明 |
id | int | 人脸保存的唯一ID |
file | string | 人脸图片路径 |
state | int | 状态:1未入库,2已入库 |
biz_id | string | 身份ID这个字段值会作为上报接口中对应的person_id字段值 |
name | string | 名字这个字段值会作为上报接口中对应的person_name字段值 |
设置人脸身份ID
根据人脸保存的唯一ID设置照片身份ID和名字
/api/face_edit
method:post Content-Type: application/json
Request body:
{"id":801,"biz_id":"xx","name":"xxx"}
* | 类型 | 说明 |
biz_id | string | 身份ID这个字段值会作为上报接口中对应的person_id字段值 |
name | string | 名字这个字段值会作为上报接口中对应的person_name字段值 |
人脸识别记录查询
分页查询人脸识别历史记录
/api/recognition_list?page=1&limit=16
method:get
* | 类型 | 说明 |
page | int | 页码 |
limit | int | 每页记录数 |
response body
{
"code": 20000,
"data": {
"total": 9,
"list": [
{
"id": 10,
"face": "./track_out/10.jpg",
"time": "1606189079985",
"person_id": "",
"person_face": "",
"name": "",
"origin_file": "./track_out/origin_10.jpg",
"video_file": "",
"blur": "0.1",
"goodness": "1",
"score": "0"
},
{
"id": 9,
"face": "./track_out/9.jpg",
"time": "1606188705928",
"person_id": "",
"person_face": "",
"name": "",
"blur": "0.1",
"goodness": "0.9",
"score": "0"
}
]
}
}
* | 类型 | 说明 |
id | int | id |
face | string | 抓拍的人脸保存路径 |
time | string | 识别时间(毫秒) |
person_id | string | 识别到的人脸编号 |
person_face | string | 识别到的人脸路径 |
name | string | 识别到的人名字 |
origin_file | string | 抓拍的整画面图 |
video_file | string | 抓拍的视频文件 |
blur | float | 人脸模糊程度属性(0〜1,0代表最清晰,1代表最模糊) |
goodness | float | 人脸品质,根据pose/blur,得到的人脸分数(0〜1,1是最高分,表示人脸好 |
score | string | 相似度 |
人脸入库
只有进行了入库操作的人脸才会参与识别
/api/face_extract_feature?id=x
method:get
* | 类型 | 说明 |
id | int | 人脸保存的唯一ID |
response body
{
"code": 20000,
"bizCode": 200
}
* | 类型 | 说明 |
bizCode | int | 200为成功 |
内网搜索设备
通过udp协议给5634端口广播“hi”,同一局域网内的设备会回复自身主机信息。 JAVA搜索例子
DatagramSocket dgSocket = new DatagramSocket();
dgSocket.setSoTimeout(1000);
byte b[] = "hi\n".getBytes();
DatagramPacket dgPacket = new DatagramPacket(b, b.length, InetAddress.getByName("255.255.255.255"), 5634);
dgSocket.send(dgPacket);
byte[] receiveBuf = new byte[256];
DatagramPacket dp = new DatagramPacket(receiveBuf, 0, receiveBuf.length);//定义一个接收的包
try {
while (true) {
dgSocket.receive(dp);
if (dp.getLength() > 0) {
String rs = new String(dp.getData(), 0, dp.getLength());
//id:xxxxxx,v:xxxx,m:xxxxx
String rs_slipt[] = rs.split(",");
if ((cMap.get(rs) == null) && (rs_slipt.length > 1)) {
String id = rs_slipt[0].replaceAll("id:", "");
System.out.println("id:"+id);
System.out.println("ip:"+dp.getAddress().getHostAddress());
}
}
}
iOS/C
int socketfd;
socklen_t addr_len;
char hi[]="Hi";
struct sockaddr_in server_addr;
if((socketfd = socket(PF_INET,SOCK_DGRAM,0)) < 0)
{
perror("socket");
return -1;
}
int i=1;
socklen_t len = sizeof(i);
setsockopt(socketfd,SOL_SOCKET,SO_BROADCAST,&i,len);
struct timeval tv_out;
tv_out.tv_sec = 1;//等待5秒
tv_out.tv_usec = 0;
setsockopt(socketfd,SOL_SOCKET,SO_RCVTIMEO,&tv_out, sizeof(tv_out));
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("255.255.255.255");
server_addr.sin_port = htons(atoi("5634"));
addr_len=sizeof(server_addr);
sendto(socketfd,hi,strlen(hi),0,(struct sockaddr*)&server_addr,addr_len);
printf("Broadcast message to port 5634\n");
char buff[256];
memset(&buff, 0, 256);
struct sockaddr_in sddr;
int n;
len = sizeof(sddr);
n = recvfrom(socketfd, buff, 256, 0, (struct sockaddr*)&sddr,&len);
if (n>0){
printf("%s,server ip=%s\n",buff,inet_ntoa(sddr.sin_addr));
}
close(socketfd);
视频抓拍数据流
待完善
TCP/IP协议对接
支持TCP长连接对接第三方服务器,在TCP通道可以发送指令控制设备 待完善
获取实时视频流(H264)
待完善
P2P对接协议
待完善
RTMP协议对接
待完善
SDK二次开发说明
可以二次开发扩展程序(c/c++),FTP上传到设备或NFS挂载调试。 设备应用框架采用linux so动态链接库方式扩展功能
需要具备的知识: 需要熟练掌握C/C++编程 熟悉Linux操作系统基本知识
开发第一个Hello程序
安装Linux 32位操作系统
建议虚拟机,Ubuntu18.04版本,如果是64位,需要安装32位扩展.
安装HISI Arm处理器编译器
arm-himix200-linux 下载地址 百度网盘 链接:https://pan.baidu.com/s/1IHAlX3NwITqPzXP55N4BZQ 提取码:ibn1
tar -xzvf arm-himix200-linux.tgz
cd arm-himix200-linux
chmod +x ./arm-himix200-linux.install
./arm-himix200-linux.install
执行下面命令确认编译器是否安装成功
arm-himix200-linux-gcc -v
Using built-in specs.
COLLECT_GCC=arm-himix200-linux-gcc
COLLECT_LTO_WRAPPER=/opt/hisi-linux/x86-arm/arm-himix200-linux/host_bin/../libexec/gcc/arm-linux-gnueabi/6.3.0/lto-wrapper
Target: arm-linux-gnueabi
编译第一个程序Hello
解压OPCOL SDK之后,看到如下目录结构
.OPCOLV300
│--3rd
│--admin
│--doc
│--hisi
│--plugin
│ │--hello
│ │--|--Makefile
│--sdkinclude
│--cfg.mak
│--Makefile.param
在hello的目录下执行
Make
编译成功之后生成了 libhello.so 把libhello.so拷贝到设备上的/app/plugin目录下 用telnet客户端连接到设备 执行启动主程序
cd /app
./run.sh
hello.c程序解析
#include "opcol.h" //SDK头文件
#include <stdlib.h>
int OPCOL_plugin_init(OPCOL_PLUGIN_CONTEXT_S *context)
{
//扩展SO,初始化入口
printf("Hi,i am opcol\n");
return 0;
}
int OPCOL_plugin_free()
{
//扩展SO,释放函数
return 0;
}
有2个函数 OPCOL_plugin_init为so的入口函数,主程序主动调用用户二次开发的so OPCOL_plugin_free 为主程序退出时调用的函数
SO里获取视频流
设备里有2路视频流:1路1080P 30帧/s;1路360P 20帧/s
#include "opcol.h" //SDK头文件
#include <stdlib.h>
int SNB_event_handle(OPCOL_EVENT_TYPE_E eventType, void *pstData);
int OPCOL_plugin_init(OPCOL_PLUGIN_CONTEXT_S *context)
{
//扩展SO,初始化入口
//注册事件回调函数,当有视频数据时,回调my_event_handle函数
context->eventListener = &my_event_handle;
return 0;
}
int OPCOL_plugin_free()
{
//扩展SO,释放函数
return 0;
}
//视频数据回调函数
int SNB_event_handle(OPCOL_EVENT_TYPE_E eventType, void *pstData)
{
if (eventType != OPCOL_EVENT_VENC_STREAM)
{
return 0;
}
OPCOL_VENC_STREAM_S *param = (OPCOL_VENC_STREAM_S *)pstData;
//视频流数据处理,例如保存到文件,提交到云服务器等
//....do something
if (param->chn == 1)
{
//第1通道视频数据,拷贝到内存
char *cache=(char *)malloc(1024*1024);
int len=0;
for (i = 0; i < param->pstStream->u32PackCount; i++)
{
memcpy(cache + len, pstStream->pstPack[i].pu8Addr + pstStream->pstPack[i].u32Offset, pstStream->pstPack[i].u32Len - pstStream->pstPack[i].u32Offset);
len = len + pstStream->pstPack[i].u32Len - pstStream->pstPack[i].u32Offset;
}
free(cache);
}
return 0;
}
SQLite3数据库
#include "opcol.h" //SDK头文件
#include <stdlib.h>
#include "sqlite3.h"
sqlite3 *db_handler;
int SNB_event_handle(OPCOL_EVENT_TYPE_E eventType, void *pstData);
int OPCOL_plugin_init(OPCOL_PLUGIN_CONTEXT_S *context)
{
//扩展SO,初始化入口
db_handler = context->db_handler; //指向数据库指针
//do something about sqlite3 db
//.....
return 0;
}
int OPCOL_plugin_free()
{
//扩展SO,释放函数
return 0;
}
SDK函数
//设置视频通道的帧率
int OPCOL_set_h264_frame_rate(int chn, int rate);
//设置视频通道的码流
int OPCOL_set_h264_bit_rate(int chn, int rate);
//编码一帧图像RAW(yuv420)数据到JPG
int OPCOL_encode_jpeg(VIDEO_FRAME_INFO_S *frame, unsigned char *out_buffer, int *out_len);
//在视频画面画框
int OPCOL_draw_rect(int x, int y, int w, int h);
//清除视频画面画框
int OPCOL_clear_rect();
//侦听系统事件
void OPCOL_plugin_dispath_event(OPCOL_EVENT_TYPE_E eType, void *data);
//设置系统密码
int OPCOL_set_password(const char *old_password, const char *new_password);
//获取签名KEY
OPCOL_ACCESS_INFO *OPCOL_get_access_info();
//设置音视频参数,如视频镜像翻转,快门曝光时间
int OPCOL_set_video_audio_attr(OPCOL_VIDEO_AUDIO_ATTR *video_audio_attr);
//获取音视频参数
OPCOL_VIDEO_AUDIO_ATTR *OPCOL_get_video_audio_attr();
//获取网络配置参数,如WIFI,IP地址,mac等
OPCOL_NET_INFO *OPCOL_get_net_info();
//设置网络参数,如WFI,ip
int OPCOL_set_net_info(OPCOL_NET_INFO *net_info);
系统事件,系统事件发生时,会广播每个so插件,每个插件在入口函数里注册事件侦听函数 如下注册my_event_handle为事件处理函数,具体看SO里获取视频流例子代码
context->eventListener = &my_event_handle;
typedef enum _opcolEVENT_TYPE_E
{
OPCOL_EVENT_VENC_STREAM = 0, //有新的视频流帧数据
OPCOL_EVENT_YUV_FRAME, //新的yuv420 raw数据帧
OPCOL_EVENT_HTTP_REQ, //当收到HTTP请求事件
OPCOL_EVENT_DISK_SPACE_COUNTED, //磁盘统计信息事件
OPCOL_EVENT_FACE_RECOGNITION, //侦测到人脸时事件
} OPCOL_EVENT_TYPE_E;