目录
前言
一、HTTP协议
1.客户端请求
2.服务器响应
3.常见响应代号和对应描述
二、服务器搭建
1.思路
2.获取请求头部
3.解析头部获取URL和请求
4.服务器响应
5.http.h
6.tcp.cpp 和tcp.h
三、主程序的实现
四、本地html文件
总结
前言
在树莓派部署yolov5lite的基础上,搭建http服务器,添加网页监控功能
一、HTTP协议
http(hyper text transfer prtocol)即超文本传输协议,利用该协议能够实现浏览器加载本地文件的功能,本文在http1.0版本的基础上实现功能。
1.客户端请求
客户端请求的格式一般由:请求行、请求头、空行和请求数据组成,具体格式如下:
请求行: 请求方法|空格|URL|空格|协议版本|回车符|换行符|
请求头: 字段名|:|值|回车符|换行符|
........
字段名|:|值|回车符|换行符|
空行: 回车符|换行符|
请求数据: xxxxxxxx
2.服务器响应
服务器响应的格式一般由:状态行、消息头、空行和响应正文组成,具体格式如下:
状态行: 协议版本|空格|响应代号|空格|代号描述|回车符|换行符|
消息头: 字段名|:|值|回车符|换行符|
........
字段名|:|值|回车符|换行符|
空行: 回车符|换行符|
响应正文: xxxxxxxx
3.常见响应代号和对应描述
| 响应代号 | 代号描述 | |
| 服务器上存在请求内容并可以响应给客户端 | 200 | OK |
| 客户端请求异常,方法有问题 | 501 | METHOD NOT IMPLEMENTED |
| 请求内容不存在 | 404 | NOT FOUND |
| 客户端发送的请求格式有问题 | 400 | BAD REQUEST |
二、服务器搭建
1.思路
http协议工作模式为客户端发送请求,服务器响应请求,客户端为浏览器,因此只需要解析客户端发来的请求并响应即可,由于yolov5在c++环境下部署,因此服务器采用c/c++语言。
2.获取请求头部
简历socket连接,read客户端发来的消息并解析
int HTTP::getline(int socketid, string &buff)
{
while(true)//遇到换行说明一行结束
{
char ch = ' ';
int len = read(socketid, &ch, 1);
if(len == 1)//正常读取
{
if(ch == 'r') continue;
else if (ch == 'n') break;
buff += ch;//string添加
}
else if (len == -1)//读取出错
{
perror("read error");
return -1;
}
else if (len == 0)//无数据
{
std::cerr<<"关闭客户端"< 0);
cout<
3.解析头部获取URL和请求
根据请求头格式,先读取一行请求行,利用空格划分字符串并获取URL和请求,如果URL格式不正确,比如url=index.htm?xxxxxxxxx,只取?前的内容
*
作用:解析请求行内容
形参:请求行内容buff,请求方法缓存request,url路径缓存
返回值:解析成功返回true,否则false
*/
bool HTTP::get_request_url(string buff, string &request, string &url)
{
if(buff == "")
return false;
std::vector strlist;
strlist.clear();
buff += " ";
size_t pos = buff.find(' ');
while(pos != buff.npos)
{
strlist.push_back(buff.substr(0, pos));
buff = buff.substr(pos+1, buff.size());
pos = buff.find(' ');
}
request = strlist[0];
url = strlist[1];
cout<<"request: "<
4.服务器响应
根据请求方法和URL进行响应,由于本文只用到了GET方法,故只需要解决GET和非GET
void HTTP::do_response(int socketid, std::string url, int status)
{
cout<<"==============================do_response========================"< 0)//读取正常
{
if(!get_request_url(buff, request, url))//解析错误
return;
if(request == "GET")
{
int status = 0;
do_response(socketid, url, status);
}
else//非GET请求,读取http头部,并响应客户端501 Mehtod Not Implemented
{
do_response(socketid, 501);
}
}
else//读取异常
{
do_response(socketid, 400);
}
}
5.http.h
#ifndef __HTTP_H
#define __HTTP_H
#include
#include "tcp.h"
#include "http.h"
#include "sys/stat.h"
#include
#include
#include
class HTTP
{
private:
const std::string status_200 = "HTTP/1.0 200 OKrn";
const std::string status_501 = "HTTP/1.0 501 METHOD NOT IMPLEMENTEDrn";
const std::string status_404 = "HTTP/1.0 404 NOT FOUNDrn";
const std::string status_400 = "HTTP/1.0 400 BAD REQUESTrn";
const std::string text_header = "Server: Run ServerrnContent-Type: text/htmlrnConnection: closernContent-Length: ";
const std::string img_header = "Server: Run ServerrnContent-Type: image/jpegrnConnection: closernContent-Length: ";
const std::string work_dir = "/home/sy/HTTPCPP/html_docs";
public:
int getline(int socketid, std::string &buff);
int gethead(int socketid, std::string &header);
bool get_request_url(std::string buff, std::string &request,std::string &url);
long int get_realurl(std::string &url, int &status);
void do_request(int socketid);
void do_response(int socketid, std::string url, int status);
void do_response(int socketid, int status);
};
#endif
6.tcp.cpp 和tcp.h
#include "tcp.h"
//domain 指定使用何种的地址类型
//PF_INET/AF_INET Ipv4 网络协议
//PF_INET6/AF_INET6 Ipv6 网络协议
int TCP::Socket(int domain,int type,int protocol)
{
int socketid = socket(domain,type,protocol);
if(socketid == -1)
{
perror("socket");
exit(1);
}
return socketid;
}
int TCP::Bind(int sockfd,struct sockaddr * my_addr,int addrlen)
{
int val = bind(sockfd,my_addr,addrlen);
if(val == -1)
{
perror("bind");
exit(0);
}
return val;
}
int TCP::Listen(int s,int backlog)
{
int val = listen(s,backlog);
if(val == -1)
{
perror("listen");
exit(0);
}
return val;
}
int TCP::Accept(int s,struct sockaddr * addr,socklen_t * addrlen)
{
int newfd = accept(s,addr,addrlen);
if(newfd < 0)
{
perror("accept");
exit(0);
}
return newfd;
}
int TCP::Connect(int s,struct sockaddr * addr,socklen_t addrlen)
{
int newfd = connect(s, addr, addrlen);
if(newfd < 0)
{
perror("connet");
exit(0);
}
return newfd;
}
#ifndef TCP_H
#define TCP_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class TCP{
public:
int Socket(int domain,int type,int protocol);
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);
int Listen(int s,int backlog);
int Accept(int s,struct sockaddr * addr,socklen_t * addrlen);
int Connect(int s,struct sockaddr * addr,socklen_t addrlen);
};
#endif
三、主程序的实现
树莓派搭建服务器采用多线程的方式,主程序负责人脸识别并保存jpg格式图片,创建线程用来实现图片和html文件传输,设置全局变量标志位,当图像文件写入完成后,线程才能发送图片,而发送图片时不能写入。
#include
#include
#include
#include
#include
#include
#include
#include "Yolo.h"
#include "base64.h"
#include "tcp.h"
#include "http.h"
#include
bool sendok = false;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void show_shape(std::vector shape)
{
std::cout< &boxes, int w_from, int h_from, int w_to, int h_to)
{
float w_ratio = float(w_to)/float(w_from);
float h_ratio = float(h_to)/float(h_from);
for(auto &box: boxes)
{
box.x1 *= w_ratio;
box.x2 *= w_ratio;
box.y1 *= h_ratio;
box.y2 *= h_ratio;
}
return ;
}
cv::Mat draw_box(cv::Mat & cv_mat, std::vector &boxes)
{
int CNUM = 80;
static const char* class_names[] = {
"face", "face_mask"
};
cv::RNG rng(0xFFFFFFFF);
cv::Scalar_ randColor[CNUM];
for (int i = 0; i < CNUM; i++)
rng.fill(randColor[i], cv::RNG::UNIFORM, 0, 256);
for(auto box : boxes)
{
int width = box.x2-box.x1;
int height = box.y2-box.y1;
char text[256];
cv::Point p = cv::Point(box.x1, box.y1-5);
cv::Rect rect = cv::Rect(box.x1, box.y1, width, height);
cv::rectangle(cv_mat, rect, cv::Scalar(0, 0, 255), 2);
sprintf(text, "%s %.1f%%", class_names[box.label], box.score * 100);
cv::putText(cv_mat, text, p, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
}
return cv_mat;
}
void* pthread_listen(void* arg)
{
TCP tcp;
HTTP http;
int socketid = tcp.Socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in httpserver;
httpserver.sin_family = AF_INET;
httpserver.sin_port = htons(80);
httpserver.sin_addr.s_addr = htonl(INADDR_ANY);
tcp.Bind(socketid, (struct sockaddr *)&httpserver, sizeof(struct sockaddr));
tcp.Listen(socketid, 100);
while (true)
{
struct sockaddr_in client;
socklen_t clientlen = sizeof(client);
char client_ip[64] = {0};
int client_sockfd = tcp.Accept(socketid, (struct sockaddr *)&client, &clientlen);
cout << "========================================================" << endl;
cout << "client ip: " << inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)) << "tport : " << ntohs(client.sin_port) << endl;
while(!sendok);
http.do_request(client_sockfd);
sendok = false;
close(client_sockfd);
}
close(socketid);
pthread_exit(NULL);
}
int main(int argc, char* argv[])
{
Base64 base64;
std::string model_name = "weights/yolov5s.mnn";
int num_classes=2;
std::vector yolov5s_layers{
{"461", 32, {{116, 90}, {156, 198}, {373, 326}}},
{"403", 16, {{30, 61}, {62, 45}, {59, 119}}},
{"345", 8, {{10, 13}, {16, 30}, {33, 23}}},
};
std::vector & layers = yolov5s_layers;
int net_size =640;
// get output data
std::string output_tensor_name0 = layers[2].name ;
std::string output_tensor_name1 = layers[1].name ;
std::string output_tensor_name2 = layers[0].name ;
std::shared_ptr net = std::shared_ptr(MNN::Interpreter::createFromFile(model_name.c_str()));
if (nullptr == net) {
return 0;
}
MNN::ScheduleConfig config;
config.numThread = 4;
config.type = static_cast(MNN_FORWARD_CPU);
MNN::BackendConfig backendConfig;
backendConfig.precision = (MNN::BackendConfig::PrecisionMode)2;
// backendConfig.precision = MNN::PrecisionMode Precision_Normal; // static_cast(Precision_Normal);
config.backendConfig = &backendConfig;
std::vector saveNamesVector;
saveNamesVector.push_back(output_tensor_name0);
saveNamesVector.push_back(output_tensor_name1);
saveNamesVector.push_back(output_tensor_name2);
config.saveTensors = saveNamesVector;
MNN::Session *session = net->createSession(config);
cv::VideoCapture capture;
capture.open(0); //修改这个参数可以选择打开想要用的摄像头
cv::Mat frame;
int INPUT_SIZE = 320;
std::string img;
pthread_mutex_init(&lock, NULL);
pthread_t id;
pthread_create(&id, NULL, pthread_listen, NULL);
while(1)
{
capture >> frame;
cv::Mat image;
cv::flip(frame, image, 1);
cv::Mat raw_image = image;
cv::resize(raw_image, image, cv::Size(INPUT_SIZE, INPUT_SIZE));
// preprocessing
image.convertTo(image, CV_32FC3);
// image = (image * 2 / 255.0f) - 1;
image = image /255.0f;
// wrapping input tensor, convert nhwc to nchw
std::vector dims{1, INPUT_SIZE, INPUT_SIZE, 3};
auto nhwc_Tensor = MNN::Tensor::create(dims, NULL, MNN::Tensor::TENSORFLOW);
auto nhwc_data = nhwc_Tensor->host();
auto nhwc_size = nhwc_Tensor->size();
std::memcpy(nhwc_data, image.data, nhwc_size);
auto inputTensor = net->getSessionInput(session, nullptr);
inputTensor->copyFromHostTensor(nhwc_Tensor);
// run network
clock_t startTime,endTime;
startTime = clock();//计时开始
net->runSession(session);
MNN::Tensor *tensor_scores = net->getSessionOutput(session, output_tensor_name0.c_str());
MNN::Tensor *tensor_boxes = net->getSessionOutput(session, output_tensor_name1.c_str());
MNN::Tensor *tensor_anchors = net->getSessionOutput(session, output_tensor_name2.c_str());
MNN::Tensor tensor_scores_host(tensor_scores, tensor_scores->getDimensionType());
MNN::Tensor tensor_boxes_host(tensor_boxes, tensor_boxes->getDimensionType());
MNN::Tensor tensor_anchors_host(tensor_anchors, tensor_anchors->getDimensionType());
tensor_scores->copyToHostTensor(&tensor_scores_host);
tensor_boxes->copyToHostTensor(&tensor_boxes_host);
tensor_anchors->copyToHostTensor(&tensor_anchors_host);
std::vector result;
std::vector boxes;
yolocv::YoloSize yolosize = yolocv::YoloSize{INPUT_SIZE,INPUT_SIZE};
float threshold = 0.45;
float nms_threshold = 0.5;
// show_shape(tensor_scores_host.shape());
// show_shape(tensor_boxes_host.shape());
// show_shape(tensor_anchors_host.shape());
boxes = decode_infer(tensor_scores_host, layers[2].stride, yolosize, net_size, num_classes, layers[2].anchors, threshold);
result.insert(result.begin(), boxes.begin(), boxes.end());
boxes = decode_infer(tensor_boxes_host, layers[1].stride, yolosize, net_size, num_classes, layers[1].anchors, threshold);
result.insert(result.begin(), boxes.begin(), boxes.end());
boxes = decode_infer(tensor_anchors_host, layers[0].stride, yolosize, net_size, num_classes, layers[0].anchors, threshold);
result.insert(result.begin(), boxes.begin(), boxes.end());
nms(result, nms_threshold);
std::cout< vecImg;
std::vector quality;
quality.push_back(CV_IMWRITE_JPEG_QUALITY);
quality.push_back(30);
cv::imencode(".jpg",frame_show, vecImg, quality);
if(!sendok)
cv::imwrite("html_docs/output.jpg", frame_show);
sendok = true;
//cv::imshow("outpt", frame_show);
//if (cv::waitKey(30) >= 0)
// break;
}
return 0;
}
四、本地html文件
接收到客户端请求后,会将本地的html文件进行传输,从而实现网页,利用刷新机制,设置图片为200ms刷新一次,从而实现视频效果。
- 响应代码为200的html
Raspi
Raspi Camera
- 响应代码为400的html
400 BAD REQUEST
400 BAD REQUEST
请求格式有问题!
- 响应代码为404的html
404 NOT FOUND
404 NOT FOUND
File is not existed!
- 响应代码为501的html
501 METHOD NOT IMPLEMENTED
501 METHOD NOT IMPLEMENTED
请求方法异常!
总结
最终实现效果如图



