【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现

🌞前言

这里我们会实现一个项目:在linux操作系统下基于OpenCV和Socket的人脸识别系统。

目录

🌞前言

🌞一、项目介绍

🌞二、项目分工

🌞三、项目难题

🌞四、实现细节

🌼4.1 关键程序

🌼4.2 运行结果

🌞五、程序分析

🌷5.2 客户端client.cpp

🌷5.3 服务端server.cpp


🌞一、项目介绍

项目简介:我们的项目是在linux操作系统下基于OpenCV和Socket的人脸识别系统。

客户端:
用于向服务器发送摄像头捕获的图像数据。

服务端:

在接收客户端发送的图像数据后,使用人脸检测算法检测图像中的人脸,并使用三种不同的人脸识别模型对检测到的人脸进行识别。然后,根据识别结果,在图像中绘制相应的标签(人名)以表示识别的结果。在绘制人脸标签时,使用了putText函数将标签绘制在原始图像上。

项目成就:我们的项目评分取得了99分,并且在考核中排名第一。

项目流程示意图:


🌞二、项目分工

在项目中,我主要负责的是

  1. 项目的整体协调和管理,包括团队沟通、进度追踪、质量控制等
  2. 项目的数据采集与标注
  3. 负责客户端和服务端的使用socket通信的代码开发
  4. 人脸检测的优化:基于给定弱分类器的Bagging集成学习算法,训练出了三个模型,通过众数投票选择最终的预测结果对人脸进行预测。
  5. 项目的路演答辩

🌞三、项目难题

1. 视频过大,难以进行网络传输

摄像头视频流中的一帧图片为480 * 640 * 3 = 921600 Bytes,一秒需要传输30帧画面,即需要网络带宽 26 MB/S,如果不对图片进行二进制编码是无法进行网络传输的,因此客户端需要对视频流进行二进制编码。

由于编码后字节数不确定,因此需要对传输进行简单协议,我们的方案是在每一帧图片传输前发送本次图片的字节大小,以让服务器明确下次所需要接受的字节数。因为字节大小的位数在4到6位不等,因此确定传输6位字节大小,小于6位的字节数,在高位填充0以达到6位(即1440填充为001440),这样即保证了传输的稳定性。

经过测试,传输带宽需求理论上降低64倍,达到了实际使用需求。

2. 视频流中的数据异常,导致客户端/服务器卡死:

对大多数显式异常进行补救处理,即尽量使得服务器运行不被异常打断,如服务器当前接收到的图片格式有误,则直接跳过本次运行,直接接收下个图片数据等一系列异常处理操作。

3. 父进程无法知道子进程是否结束

为了解决僵尸进程和孤儿进程导致的问题,我们构建了set进程池+信号机制函数,当父进程收到程序终止信号或来自子进程的终止信号,能够先终止所有的子进程,释放系统资源。

项目的进程池使用set进行构建,传统的使用vector + atomic 的构建方式无法很好的解决数据冒险的问题,原因在于虽然atomic数据类型能够保证对单个元素的操作是原子化的,但是本质原因在于对vector进行的不是原子化操作,如多进程删除vector中的多个元素,很有可能导致删除的不是正确元素,假设两个进程分别删除下标为1、2的元素,如果进程先删除了下标为1的元素,那么原来下标为2的元素此时下标将变为1,这导致了删除下标2的进程删除了原本下标为3的元素。

而set的增删改查是具体针对单个元素,删除元素是通过查找到特定元素后进行删除,本质上是删除红黑树上的节点。

注意:

"数据冒险"用于描述在处理数据时可能出现的问题或风险。它指的是当数据被不正确地处理、解释或使用时,可能导致不良的后果或意外的结果。这可能包括数据丢失、数据泄露、数据损坏或数据被误用的情况。数据冒险强调了数据质量管理和数据安全性的重要性,以避免可能造成的潜在风险和损失。

4. 人脸识别精度低

由于模型复杂度和数据集性能限制,本项目的预测性能无法十分优秀。机器学习中的传统特征匹配算法对复杂环境下的人脸识别无法尽如人意,但是本项目在此基础上设计了基于给定弱分类器的Bagging集成学习算法,其本质上是通过组合多个弱分类器,共同进行分类预测,通过众数投票选择出预测结果的一种算法,其效果往往比单一分类器更加优秀。


🌞四、实现细节

🌼4.1 关键程序

wkcv.link

#ifndef _WAKLOUIS_OPENCV_H_
#define _WAKLOUIS_OPENCV_H_#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/face.hpp>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <sys/wait.h>
#include <atomic>
#include <unordered_set>
#include <set>
#include <fstream>using namespace std;
using namespace cv;#define MAX_LEN 1000000 // 最大长度定义为1000000
#define PORT_NUM 5409 // 端口号定义为5409
#define MAX_LISTEN 10 // 最大监听数定义为10
#define HANDLER_QUIT_CODE 103 // 处理器退出代码定义为103
#define IMAGE_ROWS 480 // 图像行数定义为480
#define IMAGE_COLS 640 // 图像列数定义为640
#define IMAGE_TYPE 16 // 图像类型定义为16
#define PIC_FIGURES 6 // 图片数字位数定义为6
#define PIC_MAX_BYTES 921600 // 图片最大字节数定义为921600typedef basic_string<unsigned char> ustring; // 使用无符号字符的基本字符串类型
typedef unsigned char BYTE; // 字节类型定义为无符号字符类型void sigquitHandler(int pid); // 定义信号处理函数namespace wk
{// 将整数转换为字符串并填充零bool to_string_fill_zero(int num, BYTE *str){int pos = 0;string temp = to_string(num); // 将整数转换为字符串if (temp.size() > PIC_FIGURES) // 如果转换后的字符串长度超过预定义的位数{perror("to_string_fill_zero"); // 输出错误信息return -1; // 返回 false}else if (temp.size() == PIC_FIGURES) // 如果转换后的字符串长度与预定义的位数相等{for (auto i : temp){str[pos++] = i; // 将转换后的字符串按位存储到字节数组中}return 0; // 返回 true}else // 如果转换后的字符串长度小于预定义的位数{int res = PIC_FIGURES - temp.size(); // 计算需要填充的零的数量for (int i = 0; i < res; i++){str[pos++] = '0'; // 填充零}for (auto i : temp){str[pos++] = i; // 将转换后的字符串按位存储到字节数组中}return 0; // 返回 true}}// 将字节数组解析为整数int to_integer(BYTE *str){int num = 0;for (int i = 0; i < PIC_FIGURES; i++){int temp = str[i] - '0'; // 将字符转换为数字num = num * 10 + temp; // 计算整数值}return num; // 返回解析后的整数值}// 将字符串解析为整数int to_integer_model(string str){int num = 0;for (int i = 0; i < str.size(); i++){int temp = str[i] - '0'; // 将字符转换为数字num = num * 10 + temp; // 计算整数值}return num; // 返回解析后的整数值}}#endif

客户端client.cpp

#include "wkcv.link" // 包含自定义的头文件 "wkcv.link"struct sockaddr_in server_addr, client_addr; // 定义服务器和客户端地址结构体变量
int client_sockfd, returnValue; // 客户端套接字文件描述符和返回值变量int main(int argc, char *argv[]) // 主函数,接受命令行参数
{if (argc != 2) // 如果参数数量不为2{cout << "Format : ./client [Server ip]" << endl; // 输出正确的程序使用格式exit(-1); // 退出程序}// 创建套接字client_sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字if (client_sockfd < 0) // 如果创建套接字失败{perror("Socket"); // 输出错误信息exit(-1); // 退出程序}// 填充服务器信息string ipAddress = argv[1]; // 获取服务器IP地址bzero(&server_addr, sizeof(server_addr)); // 清零服务器地址结构体变量server_addr.sin_family = AF_INET; // 设置地址族为IPv4server_addr.sin_port = PORT_NUM; // 设置端口号为预定义常量值server_addr.sin_addr.s_addr = inet_addr((char *)ipAddress.data()); // 将IP地址转换为网络字节序,并赋值给服务器地址结构体变量// 服务器连接returnValue = connect(client_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); // 连接到服务器if (returnValue < 0) // 如果连接失败{perror("Connect"); // 输出错误信息exit(-1); // 退出程序}cout << "Connection Success to " << ipAddress << endl; // 打印连接成功的消息VideoCapture capture(0); // 打开摄像头,初始化摄像头捕获对象Mat image; // 定义Mat类型的图像对象vector<int> quality; // 定义保存图像压缩质量的向量quality.push_back(IMWRITE_JPEG_QUALITY); // 设置图像压缩参数quality.push_back(50); // 设置图像压缩质量为50vector<BYTE> data_encode; // 定义保存编码后图像数据的向量BYTE nextImageSize_s[PIC_FIGURES]; // 定义保存下一张图像大小的字节数组while (1) // 进入主循环{data_encode.clear(); // 清空编码后图像数据的向量memset(nextImageSize_s, '\0', sizeof(nextImageSize_s)); // 将下一张图像大小的字节数组清零capture >> image; // 获取摄像头捕获的图像if (image.empty() || image.data == NULL) // 如果图像为空{continue; // 跳过当前循环,继续下一次循环}imencode(".jpeg", image, data_encode, quality); // 将图像编码为JPEG格式,并存储到data_encode中int nSize = data_encode.size(); // 获取编码后图像数据的大小wk::to_string_fill_zero(nSize, nextImageSize_s); // 将图像数据大小转换为字符串并填充零,存储到nextImageSize_s数组中write(client_sockfd, nextImageSize_s, PIC_FIGURES); // 将下一张图像的大小发送到服务器BYTE *encodeImg = new BYTE[nSize]; // 动态分配内存,用于保存编码后的图像数据for (int i = 0; i < nSize; i++) // 遍历编码后的图像数据{encodeImg[i] = data_encode[i]; // 将编码后的图像数据存储到encodeImg数组中}int count = write(client_sockfd, encodeImg, nSize); // 将编码后的图像数据发送到服务器cout << "sent " << count << endl; // 打印发送的字节数flip(image, image, 1); // 翻转图像,使其显示在窗口中imshow("client", image); // 显示图像到窗口中if (waitKey(30) > 0) // 等待按键输入,若检测到按键输入{break; // 跳出循环}usleep(33333); // 等待一段时间}close(client_sockfd); // 关闭套接字return 0; // 退出程序
}

服务端server.cpp

#include "wkcv.link"struct sockaddr_in server_addr, client_addr;
char buffer_[MAX_LEN]{0};
int server_sockfd, client_commfd, returnValue;
set<pid_t> childLists;// 显示的标签
string name[] = {"LiYuan", "liuZhiCong", "HuangYiFeng", "LeiKunRu","LinJingYang", "TanXin", "ZhangGuanYu", "ZhaoYuQiu", "XieDunJie","FangChengTao", "LiXueZhi", "XiaXuan", "WuWenFeng", "LiuJunFeng","LiXingHai", "ZhangZhenZhou", "ChenDaLi", "YaoYiJie", "ZhangYueYang","ZhangBeiJing", "HaoJingNa", "WuKe", "YangFeiXiang", "LiuBao", "YangJiaMing","ZhangSuJun"};int main()
{// 加载人脸识别模型Ptr<face::LBPHFaceRecognizer> modelLBPH = face::LBPHFaceRecognizer::create();modelLBPH->read("../../model/save/MyFaceLBPHModel.xml");Ptr<face::FisherFaceRecognizer> modelFisher = face::FisherFaceRecognizer::create();modelFisher->read("../../model/save/MyFaceFisherModel.xml");Ptr<face::FaceRecognizer> modelPCA = face::EigenFaceRecognizer::create();modelPCA->read("../../model/save/MyFacePCAModel.xml");// 创建套接字server_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd < 0){perror("Socket");return -1;}// 填充服务器地址信息server_addr.sin_family = AF_INET;server_addr.sin_port = PORT_NUM;server_addr.sin_addr.s_addr = INADDR_ANY;// 填充bzero(&server_addr.sin_zero, sizeof(server_addr.sin_zero));// 设置套接字选项避免地址使用错误int on = 1;if ((setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0){perror("setsockopt failed");return -1;}// 绑定returnValue = bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));if (returnValue < 0){perror("Bind");exit(-1);}// 侦听returnValue = listen(server_sockfd, MAX_LISTEN);if (returnValue < 0){perror("Listen");exit(-1);}int connectionNum = 0;// 使用并发服务器模型,始终准备接收客户端连接请求while (1){// 输出等待连接的消息及连接次数cout << "Waiting Connection " << ++connectionNum << " ... " << endl;// 等待接受客户端发来的连接请求unsigned int len = sizeof(client_addr);client_commfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);if (client_commfd < 0) // 如果接受连接失败{perror("Accept"); // 输出错误信息continue;         // 继续等待下一个连接请求}// 输出与客户端连接成功的消息及客户端IP地址cout << "Get connection with : " << inet_ntoa(client_addr.sin_addr) << endl;// 设置信号处理函数signal(SIGQUIT, sigquitHandler);// 创建子进程处理客户端请求pid_t son = fork();if (son < 0) // 如果创建子进程失败{perror("Fork");    // 输出错误信息sigquitHandler(0); // 调用信号处理函数exit(1);           // 退出程序}childLists.insert(son); // 将子进程加入进程池if (son > 0) // 如果是父进程{continue; // 继续监听新的连接}// 子进程继续执行以下代码BYTE buffer_[PIC_MAX_BYTES];            // 定义存储图像数据的缓冲区BYTE nextImageSize_s[PIC_FIGURES];      // 定义存储下一张图像大小的缓冲区ustring full_buffer_;                   // 定义存储完整图像数据的字符串vector<BYTE> image_s_encoded;           // 定义存储编码后图像数据的向量int exitFlag = 0, count, nextImageSize; // 定义退出标志、读取字节数、下一张图像大小等变量// 人脸检测部分变量初始化CascadeClassifier cascade;                                            // 创建级联分类器对象cascade.load("../../model/save/haarcascade_frontalface_default.xml"); // 加载人脸检测模型vector<Rect> faces;                                                   // 定义存储检测到的人脸矩形区域的向量// 人脸识别部分,加载预训练的人脸识别模型// 循环接收客户端发送的图像数据并处理while (1){// 清空数据image_s_encoded.clear();                             // 清空编码后图像数据向量memset(buffer_, '\0', sizeof(buffer_));              // 清空图像数据缓冲区memset(nextImageSize_s, 0, sizeof(nextImageSize_s)); // 清空下一张图像大小缓冲区full_buffer_.clear();                                // 清空完整图像数据字符串// 读取下一张图像大小信息read(client_commfd, nextImageSize_s, PIC_FIGURES);nextImageSize = wk::to_integer(nextImageSize_s); // 将缓冲区转换为整数,表示图像大小int received = 0;// 循环读取图像数据,直到接收完整while (1){count = read(client_commfd, buffer_, nextImageSize - received); // 读取图像数据if (count < 0)                                                  // 如果读取失败{break; // 跳出循环}for (int i = received; i < received + count; i++){full_buffer_[i] = buffer_[i - received]; // 将数据存入完整图像数据字符串中}received += count;             // 更新已接收的数据量full_buffer_[received] = '\0'; // 在字符串末尾添加结束符if (received == nextImageSize) // 如果接收完整{break; // 跳出循环}}// 如果累计100帧没有输入信号,则中断该进程if (count == -1 || count == 0){exitFlag++;          // 增加退出标志if (exitFlag == 100) // 如果累计到100帧{destroyWindow(to_string(getpid()));                  // 销毁窗口cout << getpid() << " Client loss, exiting" << endl; // 输出客户端丢失连接信息close(client_commfd);                                // 关闭客户端连接break;                                               // 跳出循环,结束子进程}continue; // 继续下一次循环}else // 如果接收到数据{exitFlag = 0; // 重置退出标志}// 将图像数据存入向量int temp = 0;while (temp < nextImageSize){image_s_encoded.push_back(full_buffer_[temp++]); // 存入图像数据向量}// 解码图像数据Mat imageColor = imdecode(image_s_encoded, IMREAD_COLOR); // 解码为彩色图像if (imageColor.data == NULL)                              // 如果解码失败{continue; // 继续下一次循环}Mat image;cvtColor(imageColor, image, COLOR_BGR2GRAY); // 转换为灰度图像// 人脸检测flip(imageColor, imageColor, 1); // 图像翻转flip(image, image, 1);faces.clear();                                                    // 清空人脸矩形区域向量cascade.detectMultiScale(image, faces, 1.1, 20, 0, Size(70, 70)); // 检测人脸矩形区域// 遍历检测到的人脸for (int i = 0; i < faces.size(); i++){// 如果人脸区域大小不合适,则跳过if (faces[i].width <= 0 || faces[i].height <= 0 || faces[i].x + faces[i].width > 640 || faces[i].y + faces[i].height > 480){perror("Size"); // 输出错误信息continue;       // 继续下一次循环}RNG rng(i);                                                          // 随机数生成器Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), 20); // 随机颜色// 在图像中绘制人脸矩形区域rectangle(imageColor, faces[i], color, 2, 8, 0);// 截取人脸区域并调整大小Mat part = image(faces[i]);Size dsize = Size(92, 112);resize(part, part, dsize, 0, 0, INTER_AREA);// 使用三种不同的人脸识别模型进行预测int label1, label2, label3;double confidence1, confidence2, confidence3;modelLBPH->predict(part, label1, confidence1);   // LBPH算法预测modelFisher->predict(part, label2, confidence2); // Fisher算法预测modelPCA->predict(part, label3, confidence3);    // PCA算法预测// 根据预测结果绘制标签到图像中if (label1 == label2 || label1 == label3){putText(imageColor, name[label1], Point(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height), cv::FONT_HERSHEY_TRIPLEX, 1, color); // 输出姓名}else if (label2 == label3){putText(imageColor, name[label2], Point(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height), cv::FONT_HERSHEY_TRIPLEX, 1, color); // 输出姓名}else{putText(imageColor, "Unidentified", Point(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height), cv::FONT_HERSHEY_TRIPLEX, 1, color); // 输出未识别信息}}// 在窗口中显示图像imshow(to_string(getpid()), imageColor);if (waitKey(17) > 0) // 等待按键输入{break; // 跳出循环,结束子进程}}}// 关闭客户端和服务器套接字close(client_commfd);close(server_sockfd);return 0;
}// 信号处理函数,用于结束子进程
void sigquitHandler(int pid)
{// 循环遍历子进程列表for (auto i : childLists){cout << i << " Exiting" << endl; // 输出子进程退出信息kill(i, SIGTERM);                // 向子进程发送终止信号}pid_t child_pid;while ((child_pid = wait(nullptr)) > 0) // 等待所有子进程退出;_exit(HANDLER_QUIT_CODE); // 退出信号处理函数
}

🌼4.2 运行结果

测试成员一出现在摄像头面前,显示成员一的姓名标签:

测试成员二出现在摄像头面前,显示成员二的姓名标签:

测试成员三出现在摄像头面前,显示成员三的姓名标签:


🌞五、程序分析

wkcv.link是一个C++头文件,定义了一些常量、类型和函数。让我们详细分析一下:

1. 包含头文件:opencv、socket

#ifndef _WAKLOUIS_OPENCV_H_
#define _WAKLOUIS_OPENCV_H_#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/face.hpp>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <sys/wait.h>
#include <atomic>
#include <unordered_set>
#include <set>
#include <fstream>

包含一些标准的C++和OpenCV的头文件,还有一些与网络通信相关的头文件。


2. 定义命名空间wkstd+常量

using namespace std;
using namespace cv;#define MAX_LEN 1000000 // 最大长度定义为1000000
#define PORT_NUM 5409 // 端口号定义为5409
#define MAX_LISTEN 10 // 最大监听数定义为10
#define HANDLER_QUIT_CODE 103 // 处理器退出代码定义为103
#define IMAGE_ROWS 480 // 图像行数定义为480
#define IMAGE_COLS 640 // 图像列数定义为640
#define IMAGE_TYPE 16 // 图像类型定义为16
#define PIC_FIGURES 6 // 图片数字位数定义为6
#define PIC_MAX_BYTES 921600 // 图片最大字节数定义为921600typedef basic_string<unsigned char> ustring; // 使用无符号字符的基本字符串类型
typedef unsigned char BYTE; // 字节类型定义为无符号字符类型void sigquitHandler(int pid); // 定义信号处理函数

定义一些常量:包括最大长度 MAX_LEN, 端口号 PORT_NUM, 最大监听数 MAX_LISTEN, 处理器退出代码 HANDLER_QUIT_CODE, 图像行数 IMAGE_ROWS, 图像列数 IMAGE_COLS, 图像类型 IMAGE_TYPE, 图片数字位数 PIC_FIGURES, 以及图片最大字节数 PIC_MAX_BYTES


3. 命名空间wk定义了三个函数

namespace wk
{// 将整数转换为字符串并填充零bool to_string_fill_zero(int num, BYTE *str){int pos = 0;string temp = to_string(num); // 将整数转换为字符串if (temp.size() > PIC_FIGURES) // 如果转换后的字符串长度超过预定义的位数{perror("to_string_fill_zero"); // 输出错误信息return -1; // 返回 false}else if (temp.size() == PIC_FIGURES) // 如果转换后的字符串长度与预定义的位数相等{for (auto i : temp){str[pos++] = i; // 将转换后的字符串按位存储到字节数组中}return 0; // 返回 true}else // 如果转换后的字符串长度小于预定义的位数{int res = PIC_FIGURES - temp.size(); // 计算需要填充的零的数量for (int i = 0; i < res; i++){str[pos++] = '0'; // 填充零}for (auto i : temp){str[pos++] = i; // 将转换后的字符串按位存储到字节数组中}return 0; // 返回 true}}// 将字节数组解析为整数int to_integer(BYTE *str){int num = 0;for (int i = 0; i < PIC_FIGURES; i++){int temp = str[i] - '0'; // 将字符转换为数字num = num * 10 + temp; // 计算整数值}return num; // 返回解析后的整数值}// 将字符串解析为整数int to_integer_model(string str){int num = 0;for (int i = 0; i < str.size(); i++){int temp = str[i] - '0'; // 将字符转换为数字num = num * 10 + temp; // 计算整数值}return num; // 返回解析后的整数值}}#endif

在命名空间 wk 中定义了几个函数

bool to_string_fill_zero(int num, BYTE *str)
这段函数的作用是将整数转换为字符串并存在字节数组中,并根据预定义的位数填充零。具体步骤如下:

  1. 首先将整数转换为字符串。
  2. 如果转换后的字符串长度超过预定义的位数 PIC_FIGURES,则输出错误信息并返回 false。
  3. 如果转换后的字符串长度与预定义的位数相等,则将转换后的字符串按位存储到字节数组中,并返回 true。
  4. 如果转换后的字符串长度小于预定义的位数,则计算需要填充的零的数量,并在字节数组中填充零,然后将转换后的字符串按位存储到字节数组中,并返回 true。

int to_integer(BYTE *str)
这段程序的作用是将字节数组解析为一个整数。具体步骤如下:

  1. 初始化一个整数 num 为 0。
  2. 使用一个循环遍历字节数组 str 的前 PIC_FIGURES 个元素。
  3. 将每个字符减去字符 '0' 的 ASCII 值,将其转换为对应的数字。
  4. 根据位置权重,将每个数字乘以 10 的相应次方并加到 num 上,得到最终的整数值。
  5. 返回解析后的整数值。

int to_integer_model(string str)
这段程序的作用是将一个字符串解析为一个整数。具体步骤如下:

  1. 初始化一个整数 num 为 0。
  2. 使用一个循环遍历字符串 str 的每个字符。
  3. 将每个字符减去字符 '0' 的 ASCII 值,将其转换为对应的数字。
  4. 根据位置权重,将每个数字乘以 10 的相应次方并加到 num 上,得到最终的整数值。
  5. 返回解析后的整数值。

🌷5.2 客户端client.cpp

client.cpp是一个客户端程序,用于与服务器进行通讯。让我们分步来看:

1. 命令行参数检查

    if (argc != 2) // 如果参数数量不为2{cout << "Format : ./client [Server ip]" << endl; // 输出正确的程序使用格式exit(-1); // 退出程序}

这段代码是在程序开始时对命令行参数进行检查。程序预期接收两个参数:服务端的IP地址和端口号。argc表示命令行参数的数量,argv是一个指向参数数组的指针。

argc != 2:检查参数数量是否等于2,如果不等于2,说明用户没有提供正确的参数数量。
这里执行客户端命令用的是./client 2003。参数分别是:

  • ./client 2003:表示程序名称。
  • 2003:表示服务端的通讯端口。

2.创建客户端socket

    // 创建套接字client_sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字if (client_sockfd < 0) // 如果创建套接字失败{perror("Socket"); // 输出错误信息exit(-1); // 退出程序}

这段程序的作用是创建客户端的套接字(socket),并进行创建的错误检查。程序分析:

  • int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    这行代码创建了一个套接字,其中:
    AF_INET 指定了套接字的地址族为IPv4。
    SOCK_STREAM 指定了套接字的类型为流式套接字,即TCP套接字。
    0 表示使用默认的协议。
  • if (sockfd < -1)
    这个条件判断检查套接字是否创建成功。如果套接字创建失败,socket() 函数返回 -1,程序通过 perror("socket") 输出相关错误信息,然后返回 -1 表示程序执行失败。

3. 将服务端发送连接请求

    // 向服务器发起连接请求string ipAddress = argv[1]; // 获取服务器IP地址bzero(&server_addr, sizeof(server_addr)); // 清零服务器地址结构体变量server_addr.sin_family = AF_INET; // 设置地址族为IPv4server_addr.sin_port = PORT_NUM; // 设置端口号为预定义常量值server_addr.sin_addr.s_addr = inet_addr((char *)ipAddress.data()); // 将IP地址转换为网络字节序,并赋值给服务器地址结构体变量returnValue = connect(client_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); // 连接到服务器if (returnValue < 0) // 如果连接失败{perror("Connect"); // 输出错误信息exit(-1); // 退出程序}cout << "Connection Success to " << ipAddress << endl; // 打印连接成功的消息

这段代码的作用是向服务器发起连接请求,并在连接成功或失败时进行相应的处理和输出。具体来说:

  1. 从命令行参数中获取服务器的 IP 地址,该 IP 地址作为连接目标。
  2. 使用 bzero() 函数清零了一个用于存储服务器地址信息的结构体变量 server_addr,以确保其所有字段都是零。
  3. 设置了 server_addr 结构体的成员:
    • sin_family 设置为 AF_INET,表示使用 IPv4 地址族。
    • sin_port 设置为预定义的端口号常量 PORT_NUM,表示连接的目标端口。
    • sin_addr.s_addr 使用 inet_addr() 将 IP 地址转换为网络字节序,并将结果赋值给 server_addr 结构体的 sin_addr 成员。
  4. 调用 connect() 函数,向服务器发起连接请求,参数包括客户端套接字描述符 client_sockfd,指向 server_addr 结构体的指针,以及结构体的大小。
  5. 检查 connect() 的返回值,如果返回值小于 0,说明连接失败,使用 perror() 输出错误信息,然后调用 exit() 退出程序。
  6. 如果连接成功,使用 cout 输出连接成功的消息,其中包括连接的目标 IP 地址。

4. 打开默认摄像头

    //捕获摄像头图像VideoCapture capture(0); // 打开摄像头,初始化摄像头捕获对象Mat image; // 定义Mat类型的图像对象vector<int> quality; // 定义保存图像压缩质量的向量quality.push_back(IMWRITE_JPEG_QUALITY); // 设置图像压缩参数quality.push_back(50); // 设置图像压缩质量为50vector<BYTE> data_encode; // 定义保存编码后图像数据的向量BYTE nextImageSize_s[PIC_FIGURES]; // 定义保存下一张图像大小的字节数组

这段程序的作用是捕获摄像头图像。具体步骤如下:

  1. 使用 VideoCapture 类打开摄像头,初始化摄像头捕获对象 capture
  2. 定义 Mat 类型的图像对象 image,用于存储捕获到的图像。
  3. 定义一个 vector<int> 类型的向量 quality,用于保存图像压缩质量参数。
  4. 设置图像压缩参数,将压缩质量设置为50,并将其存入 quality 向量中。
  5. 定义一个 vector<BYTE> 类型的向量 data_encode,用于保存编码后的图像数据。
  6. 定义一个字节数组 nextImageSize_s,用于保存下一张图像大小的信息。

5. 编码的视频流传输

 while (1) // 进入主循环{data_encode.clear(); // 清空编码后图像数据的向量memset(nextImageSize_s, '\0', sizeof(nextImageSize_s)); // 将下一张图像大小的字节数组清零capture >> image; // 获取摄像头捕获的图像if (image.empty() || image.data == NULL) // 如果图像为空{continue; // 跳过当前循环,继续下一次循环}imencode(".jpeg", image, data_encode, quality); // 将图像编码为JPEG格式,并存储到data_encode中int nSize = data_encode.size(); // 获取编码后图像数据的大小wk::to_string_fill_zero(nSize, nextImageSize_s); // 将图像数据大小转换为字符串并填充零,存储到nextImageSize_s数组中write(client_sockfd, nextImageSize_s, PIC_FIGURES); // 将下一张图像的大小发送到服务器BYTE *encodeImg = new BYTE[nSize]; // 动态分配内存,用于保存编码后的图像数据for (int i = 0; i < nSize; i++) // 遍历编码后的图像数据{encodeImg[i] = data_encode[i]; // 将编码后的图像数据存储到encodeImg数组中}int count = write(client_sockfd, encodeImg, nSize); // 将编码后的图像数据发送到服务器cout << "sent " << count << endl; // 打印发送的字节数flip(image, image, 1); // 翻转图像,使其显示在窗口中imshow("client", image); // 显示图像到窗口中if (waitKey(30) > 0) // 等待按键输入,若检测到按键输入{break; // 跳出循环}usleep(33333); // 等待一段时间}

这段程序的作用是在一个无限循环中捕获摄像头图像,将图像编码为JPEG格式,并将编码后的图像数据发送到服务器。具体步骤如下:

在一个无限循环中,不断执行以下操作:

  • 清空编码后图像数据的向量 data_encode
  • 将下一张图像大小的字节数组 nextImageSize_s 清零。
  • 使用 capture >> image 获取摄像头捕获的图像。
  • 如果图像为空或者图像数据为空,则跳过当前循环,继续下一次循环。
  • 使用 imencode() 函数将图像编码为JPEG格式,并将编码后的图像数据存储到 data_encode 向量中。
  • 获取编码后图像数据的大小,并将其转换为字符串并填充零,存储到 nextImageSize_s 数组中。
  • 使用 write() 函数将下一张图像的大小发送到服务器。
  • 动态分配内存,用于保存编码后的图像数据,并将编码后的图像数据发送到服务器。
  • 打印发送的字节数。
  • 翻转图像,以便在窗口中正常显示。
  • 显示图像到名为 "client" 的窗口中。
  • 使用 waitKey() 函数等待按键输入,如果检测到按键输入,则跳出循环。
  • 使用 usleep() 函数等待一段时间,以控制图像发送的频率。

注意:这段代码中的窗口是由 OpenCV 库提供的功能创建的。使用了 imshow() 函数来显示图像在一个名为 "client" 的窗口中,而这个窗口是由 OpenCV 提供的图像显示功能创建的。


6.关闭socket

    //关闭连接close(client_sockfd); // 关闭套接字

close()函数用于关闭客户端套接字,释放资源。


🌷5.3 服务端server.cpp

1. 手写标签

//显示的标签
string name[] = {"LiYuan", "liuZhiCong", "HuangYiFeng", "LeiKunRu","LinJingYang", "TanXin", "ZhangGuanYu", "ZhaoYuQiu", "XieDunJie","FangChengTao", "LiXueZhi", "XiaXuan", "WuWenFeng", "LiuJunFeng","LiXingHai", "ZhangZhenZhou", "ChenDaLi", "YaoYiJie", "ZhangYueYang","ZhangBeiJing", "HaoJingNa", "WuKe", "YangFeiXiang", "LiuBao", "YangJiaMing","ZhangSuJun"};

2. 加载人脸识别模型

    // 加载人脸识别模型Ptr<face::LBPHFaceRecognizer> modelLBPH = face::LBPHFaceRecognizer::create();modelLBPH->read("../../model/save/MyFaceLBPHModel.xml");Ptr<face::FisherFaceRecognizer> modelFisher = face::FisherFaceRecognizer::create();modelFisher->read("../../model/save/MyFaceFisherModel.xml");Ptr<face::FaceRecognizer> modelPCA = face::EigenFaceRecognizer::create();modelPCA->read("../../model/save/MyFacePCAModel.xml");

它使用了 OpenCV 的人脸识别模块中的三种不同的识别器:LBPH、Fisher、 PCA。这些模型在之前通过训练得到,并保存在 XML 文件中。

通过 read() 方法,这些模型从 XML 文件中加载到程序中,以便后续在图像上进行人脸识别。


1. 创建服务端的socket

    // 创建套接字server_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd < 0){perror("Socket");return -1;}

这段代码的作用是创建一个套接字,用于在服务器端监听客户端的连接请求。具体来说:

  • 使用 socket() 函数创建一个套接字,指定地址族为
    IPv4(AF_INET)
    类型为流式套接字(SOCK_STREAM)
    协议为默认协议(0)。
  • 如果创建套接字失败(返回值小于 0),则输出错误信息并返回 -1 表示失败。

这段代码通常用于服务器端程序的初始化阶段,用于准备接受客户端的连接请求。


2.绑定IP地址和端口

    // 填充服务器地址信息server_addr.sin_family = AF_INET;server_addr.sin_port = PORT_NUM;server_addr.sin_addr.s_addr = INADDR_ANY;// 填充bzero(&server_addr.sin_zero, sizeof(server_addr.sin_zero));// 设置套接字选项避免地址使用错误int on = 1;if ((setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0){perror("setsockopt failed");return -1;}// 绑定returnValue = bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));if (returnValue < 0){perror("Bind");exit(-1);}

这段程序的作用是配置服务器套接字的地址信息,并将套接字与特定的网络地址和端口号绑定在一起,以便服务器能够接受客户端的连接请求。具体来说:

  • 通过 server_addr.sin_family = AF_INET; 设置地址族为 IPv4。
  • 通过 server_addr.sin_port = PORT_NUM; 设置端口号为预定义的常量 PORT_NUM
  • 通过 server_addr.sin_addr.s_addr = INADDR_ANY; 设置 IP 地址为服务器的任意可用地址。
  • 通过 bzero(&server_addr.sin_zero, sizeof(server_addr.sin_zero)); 清零结构体中未使用的部分。
  • 通过 setsockopt() 函数设置套接字选项 SO_REUSEADDR,以便在服务器重启后可以立即重用先前使用的地址和端口。
  • 最后,通过 bind() 函数将套接字绑定到指定的网络地址和端口号。如果绑定失败,程序会输出错误信息并退出。

3.设置监听状态

    // 侦听returnValue = listen(server_sockfd, MAX_LISTEN);if (returnValue < 0){perror("Listen");exit(-1);}

这段代码的作用是让服务器套接字开始监听连接请求,使其处于被动等待状态,以便接受客户端的连接请求。具体来说:

  • 使用 listen() 函数告诉操作系统,该套接字处于监听状态,并且可以接受来自客户端的连接请求。
  • listen() 函数的第一个参数是要监听的套接字描述符,即 server_sockfd
  • MAX_LISTEN 是一个预定义的常量,表示服务器允许排队等待处理的最大连接数。
  • 如果 listen() 函数执行失败(返回值小于 0),则输出错误信息并退出程序。

4.接受客户端连接请求

    int connectionNum = 0;// 使用并发服务器模型,始终准备接收客户端连接请求while (1){// 输出等待连接的消息及连接次数cout << "Waiting Connection " << ++connectionNum << " ... " << endl;// 等待接受客户端发来的连接请求unsigned int len = sizeof(client_addr);client_commfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);if (client_commfd < 0) // 如果接受连接失败{perror("Accept"); // 输出错误信息continue;         // 继续等待下一个连接请求}// 输出与客户端连接成功的消息及客户端IP地址cout << "Get connection with : " << inet_ntoa(client_addr.sin_addr) << endl;

这段程序的作用是创建一个并发服务器模型,它始终准备接受客户端的连接请求。具体功能包括:

  1. 初始化连接计数器 connectionNum,用于记录已经建立的连接次数。
  2. 在一个无限循环中,等待客户端的连接请求。
  3. 每次循环输出等待连接的消息以及连接次数。
  4. 使用 accept 函数接受客户端的连接请求,如果连接失败,则输出错误信息并继续等待下一个连接请求。
  5. 如果连接成功,则输出与客户端连接成功的消息以及客户端的IP地址。

5. 创建一个子进程来处理客户端的请求

       // 设置信号处理函数signal(SIGQUIT, sigquitHandler);// 创建子进程处理客户端请求pid_t son = fork();if (son < 0) // 如果创建子进程失败{perror("Fork");    // 输出错误信息sigquitHandler(0); // 调用信号处理函数exit(1);           // 退出程序}childLists.insert(son); // 将子进程加入进程池if (son > 0) // 如果是父进程{continue; // 继续监听新的连接}// 子进程继续执行以下代码BYTE buffer_[PIC_MAX_BYTES];            // 定义存储图像数据的缓冲区BYTE nextImageSize_s[PIC_FIGURES];      // 定义存储下一张图像大小的缓冲区ustring full_buffer_;                   // 定义存储完整图像数据的字符串vector<BYTE> image_s_encoded;           // 定义存储编码后图像数据的向量int exitFlag = 0, count, nextImageSize; // 定义退出标志、读取字节数、下一张图像大小等变量

这段程序的作用是创建一个子进程来处理客户端的请求。具体功能包括:

  1. 设置信号处理函数,当接收到 SIGQUIT 信号时调用 sigquitHandler 函数。
  2. 使用 fork() 函数创建子进程,如果创建失败,则输出错误信息,并调用信号处理函数,然后退出程序。
  3. 如果成功创建子进程,则将子进程的 PID 添加到进程池 childLists 中。
  4. 如果当前进程是父进程,则继续监听新的连接请求。
  5. 如果当前进程是子进程,则执行子进程处理的代码段,该代码段负责处理客户端请求。

6. 接受数据+人脸识别

        // 人脸识别部分,加载预训练的人脸识别模型// 人脸检测部分变量初始化CascadeClassifier cascade;                                            // 创建级联分类器对象cascade.load("../../model/save/haarcascade_frontalface_default.xml"); // 加载人脸检测模型vector<Rect> faces;                                                   // 定义存储检测到的人脸矩形区域的向量// 循环接收客户端发送的图像数据并处理while (1){// 清空数据image_s_encoded.clear();                             // 清空编码后图像数据向量memset(buffer_, '\0', sizeof(buffer_));              // 清空图像数据缓冲区memset(nextImageSize_s, 0, sizeof(nextImageSize_s)); // 清空下一张图像大小缓冲区full_buffer_.clear();                                // 清空完整图像数据字符串// 读取下一张图像大小信息read(client_commfd, nextImageSize_s, PIC_FIGURES);nextImageSize = wk::to_integer(nextImageSize_s); // 将缓冲区转换为整数,表示图像大小int received = 0;// 循环读取图像数据,直到接收完整while (1){count = read(client_commfd, buffer_, nextImageSize - received); // 读取图像数据if (count < 0)                                                  // 如果读取失败{break; // 跳出循环}for (int i = received; i < received + count; i++){full_buffer_[i] = buffer_[i - received]; // 将数据存入完整图像数据字符串中}received += count;             // 更新已接收的数据量full_buffer_[received] = '\0'; // 在字符串末尾添加结束符if (received == nextImageSize) // 如果接收完整{break; // 跳出循环}}// 如果累计100帧没有输入信号,则中断该进程if (count == -1 || count == 0){exitFlag++;          // 增加退出标志if (exitFlag == 100) // 如果累计到100帧{destroyWindow(to_string(getpid()));                  // 销毁窗口cout << getpid() << " Client loss, exiting" << endl; // 输出客户端丢失连接信息close(client_commfd);                                // 关闭客户端连接break;                                               // 跳出循环,结束子进程}continue; // 继续下一次循环}else // 如果接收到数据{exitFlag = 0; // 重置退出标志}// 将图像数据存入向量int temp = 0;while (temp < nextImageSize){image_s_encoded.push_back(full_buffer_[temp++]); // 存入图像数据向量}// 解码图像数据Mat imageColor = imdecode(image_s_encoded, IMREAD_COLOR); // 解码为彩色图像if (imageColor.data == NULL)                              // 如果解码失败{continue; // 继续下一次循环}Mat image;cvtColor(imageColor, image, COLOR_BGR2GRAY); // 转换为灰度图像// 人脸检测flip(imageColor, imageColor, 1); // 图像翻转flip(image, image, 1);faces.clear();                                                    // 清空人脸矩形区域向量cascade.detectMultiScale(image, faces, 1.1, 20, 0, Size(70, 70)); // 检测人脸矩形区域// 遍历检测到的人脸for (int i = 0; i < faces.size(); i++){// 如果人脸区域大小不合适,则跳过if (faces[i].width <= 0 || faces[i].height <= 0 || faces[i].x + faces[i].width > 640 || faces[i].y + faces[i].height > 480){perror("Size"); // 输出错误信息continue;       // 继续下一次循环}RNG rng(i);                                                          // 随机数生成器Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), 20); // 随机颜色// 在图像中绘制人脸矩形区域rectangle(imageColor, faces[i], color, 2, 8, 0);// 截取人脸区域并调整大小Mat part = image(faces[i]);Size dsize = Size(92, 112);resize(part, part, dsize, 0, 0, INTER_AREA);// 使用三种不同的人脸识别模型进行预测int label1, label2, label3;double confidence1, confidence2, confidence3;modelLBPH->predict(part, label1, confidence1);   // LBPH算法预测modelFisher->predict(part, label2, confidence2); // Fisher算法预测modelPCA->predict(part, label3, confidence3);    // PCA算法预测// 根据预测结果绘制标签到图像中if (label1 == label2 || label1 == label3){putText(imageColor, name[label1], Point(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height), cv::FONT_HERSHEY_TRIPLEX, 1, color); // 输出姓名}else if (label2 == label3){putText(imageColor, name[label2], Point(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height), cv::FONT_HERSHEY_TRIPLEX, 1, color); // 输出姓名}else{putText(imageColor, "Unidentified", Point(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height), cv::FONT_HERSHEY_TRIPLEX, 1, color); // 输出未识别信息}}// 在窗口中显示图像imshow(to_string(getpid()), imageColor);if (waitKey(17) > 0) // 等待按键输入{break; // 跳出循环,结束子进程}}}

这段代码的作用是:

  1. 加载预训练的人脸检测模型,创建级联分类器对象 CascadeClassifier,用于检测图像中的人脸。
  2. 循环接收客户端发送的图像数据,并处理每一帧图像。
  3. 清空相关数据,准备接收下一张图像的数据。
  4. 读取客户端发送的下一张图像大小信息。
  5. 循环读取图像数据,直到接收完整一张图像。
  6. 如果累计100帧没有接收到图像数据,则中断该进程。
  7. 将接收到的图像数据存入向量,并解码为彩色图像。
  8. 进行人脸检测,检测图像中的人脸矩形区域。
  9. 遍历检测到的人脸,对每个人脸区域进行处理:
    • 绘制人脸矩形区域在彩色图像中。
    • 截取人脸区域并调整大小,以便进行人脸识别。
    • 使用三种不同的人脸识别模型进行预测。
    • 根据预测结果在图像中绘制标签,显示人脸的姓名或未识别信息。
  10. 在窗口中显示处理后的图像,并等待按键输入。
  11. 如果接收到按键输入,则跳出循环,结束子进程。

对于这段函数

// 信号处理函数,用于处理退出信号
void sigquitHandler(int pid)
{// 循环遍历子进程列表for (auto i : childLists){cout << i << " Exiting" << endl; // 输出子进程退出信息kill(i, SIGTERM);                // 向子进程发送终止信号}pid_t child_pid;while ((child_pid = wait(nullptr)) > 0) // 等待所有子进程退出;_exit(HANDLER_QUIT_CODE); // 退出信号处理函数
}

这个函数的作用是处理退出信号。具体来说:

  1. 它在接收到退出信号时,会向所有子进程发送终止信号 SIGTERM,要求它们正常退出。
  2. 然后,等待所有子进程都退出完成。
  3. 最后,函数本身退出,使用预定义的退出码 HANDLER_QUIT_CODE

总的来说,这个函数确保了在接收到退出信号时,所有子进程都能够被正确地终止,并等待它们退出完成后再退出。


7.关闭socket,释放资源

    // 关闭客户端和服务器套接字close(client_commfd);close(server_sockfd);

这段代码的作用是关闭套接字并释放相关资源

  • close(listenfd);
    关闭服务端用于监听客户端连接请求的套接字 listenfd。一旦服务端不再需要监听新的连接请求,可以关闭这个套接字,以释放相关资源并告知操作系统不再维护该套接字的状态信息。
  • close(clientfd);
    关闭客户端连接的套接字 clientfd。一旦服务端与客户端的通信结束,可以关闭这个套接字,释放相关资源,并结束与该客户端的通信。

通过关闭套接字,程序能够清理掉所占用的系统资源,并确保程序的正常结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/736282.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

读算法的陷阱:超级平台、算法垄断与场景欺骗笔记06_共谋(下)

1. 博弈论 1.1. 当市场竞争对手之间普遍存在着误解和不信任情绪时&#xff0c;从长远来看&#xff0c;他们一半时间是在合作&#xff0c;另一半时间则是在背叛承诺 1.2. 当一方越了解对手&#xff0c;或者说可以更好地掌握对方的战略性行为时&#xff0c;他才可能找到展开合作…

软件设计不是CRUD(14):低耦合模块设计理论——行为抽象与设计模式(上)

是不是看到“设计模式”四个字,各位读者就觉得后续内容要开始讲一些假大空的内容了?各位读者是不是有这样的感受,就是单纯讲设计模式的内容,网络上能找到很多资料,但是看过这些资料后读者很难将设计模式运用到实际的工作中。甚至出现了一种声音:设计模式是没有用的,应用…

C++:vector底层剖析

文章目录 前言成员变量成员函数vector ()size_t size()size_t capacity()iterator begin()和const_iterator begin()constiterator end()和const_iterator end()const~vector()void push_back(const&T val)vector<T>(const vector<T>& v)vector<T>&a…

前端解决跨域问题( 6种方法 )

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

美团2025春招第一次笔试题

第四题 题目描述 塔子哥拿到了一个大小为的数组&#xff0c;她希望删除一个区间后&#xff0c;使得剩余所有元素的乘积未尾至少有k个0。塔子哥想知道&#xff0c;一共有多少种不同的删除方案? 输入描述 第一行输入两个正整数 n,k 第二行输入n个正整数 a_i&#xff0c;代表…

ARM TrustZone技术解析:构建嵌入式系统的安全扩展基石

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-dSk2aQ85ZR0zxnyI {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

【机器学习】进阶学习:详细解析Sklearn中的MinMaxScaler---原理、应用、源码与注意事项

【机器学习】进阶学习&#xff1a;详细解析Sklearn中的MinMaxScaler—原理、应用、源码与注意事项 这篇文章的质量分达到了97分&#xff0c;虽然满分是100分&#xff0c;但已经相当接近完美了。请您耐心阅读&#xff0c;我相信您一定能从中获得不少宝贵的收获和启发~ &#x1f…

free pascal 调用 C#程序读 Freeplane.mm文件,生成测试用例.csv文件

C# 请参阅&#xff1a;C# 用 System.Xml 读 Freeplane.mm文件&#xff0c;生成测试用例.csv文件 Freeplane 是一款基于 Java 的开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#x…

hadoop报错:HADOOP_HOME and hadoop.home.dir are unset. 解决方法

参考&#xff1a;https://blog.csdn.net/weixin_45735242/article/details/120579387 解决方法 1.下载apache-hadoop-3.1.0-winutils-master 官网下载地址&#xff1a; https://github.com/s911415/apache-hadoop-3.1.0-winutils win配置系统环境&#xff1a; 然后重启idea…

一文了解原型和原型链

本文重点概念&#xff1a; 1、所有的对象都是new一个函数创建的 2、所有的函数都有一个属性prototype&#xff0c;称为函数原型 3、函数原型得到的这个对象都有一个属性constructor,指向该函数 4、所有的对象都有一个属性&#xff1a;隐式原型__proto__&#xff0c;隐式原型…

机器学习,剪刀,石头,布

计算机视觉:剪刀,石头,步 TensorFlow AI人工智能及Machine Learning训练图集的下载建立分类模型并用图像进行训练检验模型总结当前AI Machine Learning 异常火爆,希望在MCU上使用机器学习,做图像识别的工作。看到一个剪刀,石头,步的学习程序,给大家分享一下。 TensorFl…

Buran勒索病毒通过Microsoft Excel Web查询文件进行传播

Buran勒索病毒首次出现在2019年5月&#xff0c;是一款新型的基于RaaS模式进行传播的新型勒索病毒&#xff0c;在一个著名的俄罗斯论坛中进行销售&#xff0c;与其他基于RaaS勒索病毒(如GandCrab)获得30%-40%的收入不同&#xff0c;Buran勒索病毒的作者仅占感染产生的25%的收入,…

AI创造的壁纸,每一幅都是视觉盛宴!

1、方小童在线工具集 网址&#xff1a; 方小童 该网站是一款在线工具集合的网站&#xff0c;目前包含PDF文件在线转换、随机生成美女图片、精美壁纸、电子书搜索等功能&#xff0c;喜欢的可以赶紧去试试&#xff01;

本地部署推理TextDiffuser-2:释放语言模型用于文本渲染的力量

系列文章目录 文章目录 系列文章目录一、模型下载和环境配置二、模型训练&#xff08;一&#xff09;训练布局规划器&#xff08;二&#xff09;训练扩散模型 三、模型推理&#xff08;一&#xff09;准备训练好的模型checkpoint&#xff08;二&#xff09;全参数推理&#xff…

2021年江苏省职业院校技能大赛高职组 “信息安全管理与评估”赛项任务书

2021年江苏省职业院校技能大赛高职组 “信息安全管理与评估”赛项任务书 一、赛项时间&#xff1a;二、赛项信息三、竞赛内容&#xff1a;第一阶段任务书&#xff08;300分&#xff09;任务1&#xff1a;网络平台搭建&#xff08;60分&#xff09;任务2&#xff1a;网络安全设备…

AI预测福彩3D第6弹【2024年3月11日预测--新算法重新开始计算日期】

由于周末休息了两天&#xff0c;没有更新文章&#xff0c;这两天也没有对福彩3D的预测。今天继续咱们使用AI算法来预测3D吧~ 前面我说过&#xff0c;我的目标是能让百十个各推荐7个号码&#xff0c;其中必有中奖号码&#xff0c;这就是7码定位&#xff0c;只要7码定位稳定了&am…

【前端系列】CSS 常见的选择器

CSS 常见的选择器 CSS&#xff08;层叠样式表&#xff09;是一种用于描述网页样式的标记语言&#xff0c;它定义了网页中各个元素的外观和布局。在 CSS 中&#xff0c;选择器是一种用于选择要应用样式的 HTML 元素的模式。选择器允许开发人员根据元素的类型、属性、关系等来选…

JVM3_数据库连接池虚引用ConnectionFinalizerPhantomReference引起的FullGC压力问题排查

背景 XOP服务运行期间&#xff0c;查看Grafana面板&#xff0c;发现堆内存周期性堆积&#xff0c;观察FullGC的时间&#xff0c;xxx&#xff0c;需要调查下原因 目录 垃圾收集器概述 常见的垃圾收集器分区收集策略为什么CMS没成为默认收集器 查看JVM运行时环境分析快照 Pha…

基于PCtoLCD实现OLED汉字取模方法

0 工具准备 PCtoLCD2002 NodeMCU&#xff08;ESP8266&#xff09;&#xff08;验证OLED字模效果&#xff09; 0.96寸OLED显示屏 1 基于PCtoLCD实现OLED汉字取模方法 1.1 基础知识介绍 0.96存OLED显示屏包含128x64个像素点&#xff0c;x轴方向为128个像素点&#xff0c;y轴方向…

[AutoSar]BSW_Com011 CAN IF 模块配置

目录 关键词平台说明一、CanIfCtrlDrvCfgs二 、CanIfTrcvDrvCfgs三、CanIfDispatchCfg四、CanIfBufferCfgs五、CanIfHrhCfgs六、CanIfHthCfgs七、CanIfRxPduCfgs八、CanIfTxPduCfgs九、CanIfPrivateCfg十、CanIfPublicCfg 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 …