# sdk-presenter **Repository Path**: rexdev3/sdk-presenter ## Basic Information - **Project Name**: sdk-presenter - **Description**: Presenter部署在Mind Studio所在的Linux服务器上,主要作用是推理结果的展示 - **Primary Language**: Python - **License**: BSD-3-Clause - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2020-08-25 - **Last Updated**: 2024-06-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 中文|[英文](README.md) Presenter部署在Mind Studio所在的Linux服务器上,主要作用是推理结果的展示。 Presenter包括Presenter Server与Presenter Agent。 - Presenter Agent提供一系列API,用户通过调用API向Presenter Server推送媒体消息。 - Presenter Server接收Presenter Agent发过来的人脸数据,通过浏览器进行结果展示。 ## Presenter Server - **Description** Presenter Server是展示人脸检测推理结果的软件包,该软件基于python3.5实现,并用到了第三方web框架tornado,以及底层通信框架protobuf。 Presenter Server支持图片模式和视频模式。图片模式展示单张人脸图片,视频模式以图片流的方式展示连续图片。Presenter server通过channel来标记不同的数据源,在浏览器里通过Create按钮添加channel,Delete进行删除,默认支持两路channel,分别是image和video。 - **Sample Code** presenter server的根目录在common/presenter/server,face\_detection是人脸识别app的目录,config是配置文件目录,修改config.conf进行可服务端ip和port的定制,logging.conf是logging模块的配置;src是源码目录,其中,presenter\_message\_pb2.py 定义protobuf格式,presenter\_socket\_server.py 负责并行接收人脸数据,webapp.py 负责把数据推送到Chrome,进行前台展示;ui是web界面素材所在目录。 与Presenter Agent的消息通信: Presenter Server 与Presenter Agent和Chrome的消息通信如下图所示,Chrome上发起创建channel的操作,Presenter Server发送人脸数据到指定channel,Chrome打开此channel,观察人脸检测推理结果。 ![](doc/source/img/zh-cn_image_0167567268.png) Presenter Server与Presenter Agent之间消息结构如下,依次是4个字节的消息总长度,1个字节的消息名长度,若干字节的消息消息名,若干字节的protobuf内容。 ``` -------------------------------------------------------------- |total message len | int | 4 bytes | |-------------------------------------------------------------- |message name len | byte | 1 byte | |-------------------------------------------------------------- |message name | string | xx bytes | |-------------------------------------------------------------- |message body | protobuf | xx bytes | --------------------------------------------------------------- ``` 主要的消息有两个,一个是打开channel的请求消息OpenChannelRequest ,另一个是发送人脸数据的消息PresentImageRequest ,其在ptorobuf中的定义如下: ``` message OpenChannelRequest { string channel_name = 1; //channel 名称 ChannelContentType content_type = 2; //数据模式,用来识别image和video } message PresentImageRequest { ImageFormat format = 1; // 图片格式,当前仅支持Jpeg uint32 width = 2; //图片宽度 uint32 height =3; //图片高度 bytes data = 4; //图片数据 } ``` 通过epoll实现多路channel并行工作,实现伪码如下: ``` def _server_listen_thread(self): """socket server thread, epoll listening all the socket events""" epoll = select.epoll() epoll.register(self._sock_server.fileno(), select.EPOLLIN | select.EPOLLHUP) try: conns = {} msgs = {} while True: events = epoll.poll(EPOLL_TIMEOUT) # timeout, but no event come, continue waiting if not events: continue for sock_fileno, event in events: # new connection request from presenter agent if self._sock_server.fileno() == sock_fileno: self._accept_new_socket(epoll, conns) # remote connection closed # it means presenter agent exit withot close socket. elif event & select.EPOLLHUP: self._clean_connect(sock_fileno, epoll, conns, msgs) # new data coming in a socket connection elif event & select.EPOLLIN: self._process_epollin(sock_fileno, epoll, conns, msgs) # receive event not recognize else: self._clean_connect(sock_fileno, epoll, conns, msgs) finally: epoll.unregister(self._sock_server.fileno()) epoll.close() self._sock_server.close() ``` 消息解析过程,实现伪码如下,首先是解析消息,包括消息长度,消息名,最后读取protobuf并进行处理。 ``` def _read_sock_and_process_msg(self, sock_fileno, conns, msgs): # Step1: read msg head msg_total_len, msg_name_len = self._read_msg_head(sock_fileno, conns) if msg_total_len is None: return PRESENTER_ERR # Step2: read msg name msg_name = self._read_msg_name(conns[sock_fileno], msg_name_len) if msg_name == SOCKET_RECEIVE_NULL: return PRESENTER_ERR try: msg_name = msg_name.decode("utf-8") except UnicodeDecodeError: return PRESENTER_ERR # Step3: read msg body msg_body_len = msg_total_len - MSG_HEAD_LENGTH - msg_name_len ret = self._read_msg_body(sock_fileno, conns, msgs, msg_name, msg_body_len) if ret == PRESENTER_ERR: return ret # Step4: process msg ret = self._process_msg(conns[sock_fileno], msg_name, msgs[sock_fileno]) return ret ``` 解析protobuf,来自Presenter Agent的消息请求共有三个,分别是打开channel、发送人脸数据、发送心跳。 ``` def _process_msg(self, conn, msg_name, msg_data): # process open channel request if msg_name == OPEN_CHANNEL_REQUEST_FULL_NAME: ret = self._process_open_channel(conn, msg_data) # process image request, receive an image data from presenter agent elif msg_name == PRESENT_IMAGE_REQUEST_FULL_NAME: ret = self._process_image_request(conn, msg_data) # process heartbeat request, it used to keepalive a channel path elif msg_name == HEART_BEAT_MESSAGE_FULL_NAME: ret = self._process_heartbeat(conn) else: ret = PRESENTER_ERR return ret ``` ## Present Agent Presenter Agent提供一系列API,用户可以调用这些API向Presenter Server推送媒体消息,并在浏览器中查看。当前支持JPEG格式图片的推送。 调用流程如下所示: ![](doc/source/img/zh-cn_image_0167567505.png) 1. App调用OpenChannel函数打开与Presenter Server间的通道。 2. App调用SendMessage函数在该通道上推送媒体消息。推送消息时, 支持在推送的图片上画矩形框。使用时需要将框的左上、右下点的坐标、框的标题设置到PresentImageRequest对象中。 3. 所有图片发送完成后,App调用CloseChannel函数释放分配的资源。 - **Sample Code** 以发送图片为例: 1. Open channel ``` OpenChannelParam param; param.hostIp = "127.0.0.1"; //IP address of Presenter Server param.port = 7006; //port of present service param.channelName = "image"; param.contentType = ContentType::kImage; //content type is IMAGE Channel *channel = nullptr; PresenterErrorCode errorCode = OpenChannel(channel, param); if (errorCode != PresenterErrorCode::kNone) { return; } ``` 2. SendMessage ``` ascend::presenter::proto::PresentImageRequest request; request.set_data(string(reinterpret_cast(buffer), size)); //image data buffer, image shuold be jpeg format request.set_width(1920); request.set_height(1280); //Set the rectangles info into request. ascend::presenter::proto::Rectangle_Attr *rectangle_attr = nullptr; rectangle_attr = request.add_rectangle_list(); //Add one rectangle rectangle_attr->mutable_left_top()-> set_x(100); rectangle_attr->mutable_left_top()-> set_y(100); rectangle_attr->mutable_right_bottom()->set_x(500); rectangle_attr->mutable_right_bottom()->set_y(500); rectangle_attr->set_label_text("This is a title"); //Set the title for the rectangle ascend::presenter::PresenterErrorCode error_code = ascend::presenter::SendMessage(channel, request) ``` 3. Close Channel ``` delete channel; ``` 如果需要发送一系列图片来展示视频的效果,则将[1](#zh-cn_topic_0147635264_li182791110135216)中的contentType改为ContentType::kVideo,并 不断的调用SendMessage即可。 - **修改源码** 编译源码需要使用protoc编译proto文件,请从[https://github.com/protocolbuffers/protobuf/releases/](http://code.google.com/p/protobuf/downloads/list)中获取软件包protoc-3.5.1-linux- x86\_64.zip,并参照包中的readme进行安装。 主要源码结构如下所示: ``` common/presenter/agent PresenterAgent源码根目录 ├─proto Protobuf消息定义 ├─include/ascendk/presenter/agent API头文件 ├─channel.h 通用channel的接口,提供收发protobuf消息的功能 ├─errors.h 错误码 ├─presenter_channel.h 封装了发送媒体数据到PresenterServer的功能 ├─presenter_types.h 封装了发送媒体数据到PresenterServer的功能 ├─src/asceddk/presenter/agent 源码目录 ├─channel 与PresenterServer交互相关源码 ├─default_channel.cpp Channel类的默认实现,维护与Server间的长连接 ├─default_channel.h Channel类的默认实现的头文件 ├─channel.cpp Channel类的默认实现的头文件 ├─codec ├─connection 提供发送/接收protobuf消息的接口 ├─net 网络连接相关源码, 完成收发字节数组的接口 ├─socket.cpp Socket抽象类 ├─socket_factory.cpp Socket工厂抽象类 ├─raw_socket.cpp 基于linux原生socket,不提供通道加密功能 ├─raw_socket_factory.cpp RawSocket的工厂类 ├─presenter 封装了发送媒体数据到PresenterServer的功能 ├─util 工具类相关源码 ├─Makefile ``` 如果修改了presenter\_message.proto,则需要在proto文件夹下执行以下命令编译proto文件: protoc presenter\_message.proto --cpp\_out=./