Raspberry Pi builds http server to realize web surveillance camera

Table of contents


foreword

On the basis of deploying yolov5lite on Raspberry Pi, build http server and add webpage monitoring function


1. HTTP protocol

Http (hyper text transfer prtocol) is the hypertext transfer protocol, which can realize the function of the browser to load local files. This article realizes the function on the basis of http1.0 version.

1. Client request

The format of the client request generally consists of: request line, request header, blank line and request data. The specific format is as follows:

Request line:         request method | space|URL | space | protocol version | carriage return | line feed |

Request header:         field name | : | value | carriage return | line feed |

                       ........

                       field name | : | value | carriage return | line feed |

Empty line:             carriage return | line feed |

Request data:      xxxxxxxx

 2. Server response

The format of the server response generally consists of: status line, message header, blank line and response body. The specific format is as follows:

Status line:         protocol version | space | response code | space | code description | carriage return | line feed |

Header:         field name | : | value | carriage return | line feed |

                       ........

                       field name | : | value | carriage return | line feed |

Empty line:             carriage return | line feed |

Response body:      xxxxxxxx

3. Common response codes and corresponding descriptions

response code Code Description
The request content exists on the server and can respond to the client 200 OK
The client request is abnormal, there is a problem with the method 501 METHOD NOT IMPLEMENTED
Request content does not exist 404 NOT FOUND
There is a problem with the format of the request sent by the client 400 BAD REQUEST

2. Server construction

1. Idea

The working mode of the http protocol is that the client sends a request, the server responds to the request, and the client is a browser, so it only needs to parse the request sent by the client and respond. Since yolov5 is deployed in the c++ environment, the server uses c/c++ language .

2. Get the request header

Resume socket connection, read the message sent by the client and parse it

/*
作用:获取请求行
形参:套接字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. Parse the header to get URL and request

According to the format of the request header, first read a line of the request line, use spaces to divide the string and obtain the URL and the request. If the URL format is incorrect, such as url=index.htm?xxxxxxxxx, only fetch? previous content

*
作用:解析请求行内容
形参:请求行内容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. Server response 

Respond according to the request method and URL. Since only the GET method is used in this article, only GET and non-GET need to be resolved

/*
作用:响应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;
	else
		header = 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 and 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

 3. Implementation of the main program

The Raspberry Pi builds the server in a multi-threaded manner. The main program is responsible for face recognition and saves images in jpg format. Threads are created to transmit images and html files, and global variable flags are set. After the image file is written, the thread can Send pictures, but cannot write when sending pictures.

#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 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<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));
		// 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<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 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<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;
}

 Fourth, the local html file

After receiving the request from the client, it will transmit the local html file to realize the web page, and use the refresh mechanism to set the picture to refresh every 200ms, so as to realize the video effect.

  • html with response code 200
<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>
  • html with response code 400
<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>
  • html with response code 404
<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>
  •  html with response code 501
<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>

Summarize

The final effect is as shown in the figure

Guess you like

Origin blog.csdn.net/deku_desi/article/details/125586233