失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 树莓派搭建http服务器实现网页监控摄像头

树莓派搭建http服务器实现网页监控摄像头

时间:2019-12-25 20:28:33

相关推荐

树莓派搭建http服务器实现网页监控摄像头

目录

前言

一、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.常见响应代号和对应描述

二、服务器搭建

1.思路

http协议工作模式为客户端发送请求,服务器响应请求,客户端为浏览器,因此只需要解析客户端发来的请求并响应即可,由于yolov5在c++环境下部署,因此服务器采用c/c++语言。

2.获取请求头部

简历socket连接,read客户端发来的消息并解析

/*作用:获取请求行形参:套接字id,读取内容缓存buff返回值:-1表示出错,0表示读取一个空行, >0表示成功读取一行*/int HTTP::getline(int socketid, string &buff){while(true)//遇到换行说明一行结束{char ch = '\0';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<<"关闭客户端"<<endl;return 0;}}return buff.size();}/*作用:获取请求头,遇到两次回车换行代表请求头结束形参:套接字id, 头部内容缓存返回值:0表示成功, -1表示错误*/int HTTP::gethead(int socketid, string &header){int len = 0;do{header.clear();len = getline(socketid, header);cout<<"head: "<<header<<endl;}while(len > 0);cout<<endl;return len;}

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<string> 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: "<<request<<"\turl:"<<url<<endl;return true;}/*作用:获取真正的url并定位到本地文件参数: url缓存返回值:返回url文件大小*/long int HTTP::get_realurl(string &url, int &status){url = url.substr(0, url.find('?'));struct stat info;if(stat((work_dir+url).c_str(), &info) == -1)//文件不存在或者出错,响应404{cerr<<"stat "<<(work_dir+url)<<" failed, reason:"<<strerror(errno)<<endl;}else{if(S_ISDIR(info.st_mode))//如果是目录{url = work_dir + "/200.html";}else{url = work_dir + url;}status = 200;return info.st_size;}url = work_dir + "/404.html";stat(url.c_str(), &info);status = 404;return info.st_size;}

4.服务器响应

根据请求方法和URL进行响应,由于本文只用到了GET方法,故只需要解决GET和非GET

/*作用:响应200 and 404参数:套接字id, url路径, 响应代号status*/void HTTP::do_response(int socketid, std::string url, int status){cout<<"==============================do_response========================"<<endl;int size = get_realurl(url, status);cout<<"status: "<<status<<endl;cout<<"url: "<<url.c_str()<<endl;cout<<"size: "<<size<<endl;string header;gethead(socketid, header);if(url.find("jpg") != url.npos || (url.find("png")) != url.npos || (url.find("ico")) != url.npos)header = img_header;elseheader = text_header;if(status == 200 )//url文件存在{header = status_200 + header + to_string(size) + "\r\n\r\n";}else if(status == 404)//文件不存在{header = status_404 + header + to_string(size) + "\r\n\r\n";;}cout<<"header: "<<header<<endl;//1.发送头部int len = write(socketid, header.c_str(), header.size());if(len < 0){cerr<<"socket write error"<<endl;return;}//2.发送html文件FILE* urlfd = fopen(url.c_str(), "rb");do{char buff[1024] = {0};len = fread(buff, 1, 1024, urlfd);cout<<buff;len = write(socketid, buff, len);if(len < 0){cerr<<"socket write error"<<endl;return;}}while(!feof(urlfd));fclose(urlfd);}/*作用:响应400 and 501参数:套接字id, 响应代号status*/void HTTP::do_response(int socketid, int status){cout<<"==============================do_response========================"<<endl;struct stat info;string header, url;gethead(socketid, header);if(status == 400 )//url文件存在{url = work_dir + "/400.html";stat(url.c_str(), &info);header = status_400 + text_header + to_string(info.st_size) + "\r\n\r\n";}else if(status == 501)//文件不存在{url = work_dir + "/501.html";stat(url.c_str(), &info);header = status_501 + text_header + to_string(info.st_size) + "\r\n\r\n";;}cout<<"header: "<<header<<endl;//1.发送头部int len = write(socketid, header.c_str(), header.size());if(len < 0){cerr<<"socket write error"<<endl;return;}//2.发送html文件FILE* urlfd = fopen(url.c_str(), "rb");do{char buff[1024] = {0};len = fread(buff, 1, 1024, urlfd);cout<<buff;len = write(socketid, buff, len);if(len < 0){cerr<<"socket write error"<<endl;return;}}while(!feof(urlfd));fclose(urlfd);}/*作用:处理客户端请求参数:套接字id*/void HTTP::do_request(int socketid){string buff, request, url;//1.读取请求行并解析方法int len = getline(socketid, buff);if(len > 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 <iostream>#include "tcp.h"#include "http.h"#include "sys/stat.h"#include <errno.h>#include <string>#include <vector>class HTTP{private:const std::string status_200 = "HTTP/1.0 200 OK\r\n";const std::string status_501 = "HTTP/1.0 501 METHOD NOT IMPLEMENTED\r\n";const std::string status_404 = "HTTP/1.0 404 NOT FOUND\r\n";const std::string status_400 = "HTTP/1.0 400 BAD REQUEST\r\n";const std::string text_header = "Server: Run Server\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: ";const std::string img_header = "Server: Run Server\r\nContent-Type: image/jpeg\r\nConnection: close\r\nContent-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 <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <arpa/inet.h>#include <unistd.h>#include <stdarg.h>#include <sys/stat.h>#include <fcntl.h>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 <iostream>#include <string>#include <ctime>#include <MNN/MNNDefine.h>#include <MNN/MNNForwardType.h>#include <MNN/Interpreter.hpp>#include <opencv2/opencv.hpp>#include "Yolo.h"#include "base64.h"#include "tcp.h"#include "http.h"#include <pthread.h>bool sendok = false;pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void show_shape(std::vector<int> shape){std::cout<<shape[0]<<" "<<shape[1]<<" "<<shape[2]<<" "<<shape[3]<<" "<<shape[4]<<" "<<std::endl;}void scale_coords(std::vector<BoxInfo> &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<BoxInfo> &boxes){int CNUM = 80;static const char* class_names[] = {"face", "face_mask"};cv::RNG rng(0xFFFFFFFF);cv::Scalar_<int> 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<YoloLayerData> 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<YoloLayerData> & layers = yolov5s_layers;int net_size =640;// get output datastd::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<MNN::Interpreter> net = std::shared_ptr<MNN::Interpreter>(MNN::Interpreter::createFromFile(model_name.c_str()));if (nullptr == net) {return 0;}MNN::ScheduleConfig config;config.numThread = 4;config.type= static_cast<MNNForwardType>(MNN_FORWARD_CPU);MNN::BackendConfig backendConfig;backendConfig.precision = (MNN::BackendConfig::PrecisionMode)2;// backendConfig.precision = MNN::PrecisionMode Precision_Normal; // static_cast<PrecisionMode>(Precision_Normal);config.backendConfig = &backendConfig;std::vector<std::string> 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));// preprocessingimage.convertTo(image, CV_32FC3);// image = (image * 2 / 255.0f) - 1;image = image /255.0f;// wrapping input tensor, convert nhwc to nchw std::vector<int> dims{1, INPUT_SIZE, INPUT_SIZE, 3};auto nhwc_Tensor = MNN::Tensor::create<float>(dims, NULL, MNN::Tensor::TENSORFLOW);auto nhwc_data = nhwc_Tensor->host<float>();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 networkclock_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<BoxInfo> result;std::vector<BoxInfo> 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<<result.size()<<std::endl;endTime = clock();//计时结束cout << "The forward time is: " <<(double)(endTime - startTime) / 1000.0 << "ms" << endl;//cout << "The fps is: " <<1000 / (double)(endTime - startTime)<< "frame/s" << endl;scale_coords(result, INPUT_SIZE, INPUT_SIZE, raw_image.cols, raw_image.rows);cv::Mat frame_show = draw_box(raw_image, result);std::vector<unsigned char> vecImg;std::vector<int> 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

<html lang="zh-CN" refresh=6><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"><title>Raspi</title></head><script>function refreshimg(){document.getElementById("myimg").src="output.jpg?time="+(new Date()).getTime();}setInterval("refreshimg()",200);</script><body><div align="center"><h1>Raspi Camera</h1><img class="myimg"id="myimg"src="output.jpg"vertical-align="middle"width="640px"height="480px"></div></body></html>

响应代码为400的html

<html lang="zh-CN"><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"><title>400 BAD REQUEST</title></head><body><div align="center"><h1>400 BAD REQUEST</h1><h1>请求格式有问题!</h1></div></body></html>

响应代码为404的html

<html lang="zh-CN"><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"><title>404 NOT FOUND</title></head><body><div align="center"><h1>404 NOT FOUND</h1><h1>File is not existed!</h1></div></body></html>

响应代码为501的html

<html lang="zh-CN"><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"><title>501 METHOD NOT IMPLEMENTED</title></head><body><div align="center"><h1>501 METHOD NOT IMPLEMENTED</h1><h1>请求方法异常!</h1></div></body></html>

总结

最终实现效果如图

如果觉得《树莓派搭建http服务器实现网页监控摄像头》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。