【阿里云】图像识别 智能分类识别 增加网络控制功能点(三)

news2024/12/23 13:02:41

一、增加网络控制功能

  • 实现需求
  • TCP 心跳机制解决Soket异常断开问题

二、Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。

  • 查看当前系统的TCP KeepAlive参数
  • 修改TCP KeepAlive参数

三、C语言实现TCP KeepAlive功能
四、setsockopt用于设置套接字选项的系统调用
五、代码实现
六、待定

一、增加网络控制功能

Linux网络编程(TCP Socket编程实现过程)
TCP Keepalive HOWTO

实现需求:

Socket网络编程,通过Socket远程接入控制垃圾识别功能。

TCP 心跳机制解决Soket异常断开问题

Socket客户端得断开情形无非就两种情况:

  1. 客户端能够发送状态给服务器;正常断开,强制关闭客户端等,客户端能够做出反应。
  2. 客户端不能发送状态给服务器;突然断网,断电,客户端卡死等,客户端根本没机会做出反应,服务器更不了解客户端状态,导致服务器异常等待。

为了解决上述问题,引入TCP心跳包机制:
心跳包的实现,心跳包就是服务器定时向客户端发送查询信息,如果客户端有回应就代表连接正常,类似于linux系统的看门狗机制。心跳包的机制有一种方法就是采用TCP_KEEPALIVE机制,它是一种用于检测TCP连接是否存活的机制,它的原理是在一定时间内没有数据往来时,发送探测包给对方,如果对方没有响应,就认为连接已经断开。TCP_KEEPALIVE机制可以通过设置一些参数来调整,如探测时间间隔、探测次数等。

二、Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。

在 Linux 内核中,可以通过 sysctl 命令来查看和配置 TCP KeepAlive 参数。TCP KeepAlive 是一种机制,用于在两个端点之间的连接空闲时检测连接的活跃性。

以下是一些与 TCP KeepAlive 相关的 sysctl 参数以及它们的含义:

  1. tcp_keepalive_time:
    • 含义:TCP KeepAlive 的开始时间。表示连接空闲的时间,超过这个时间,内核开始发送 KeepAlive 消息。
    • 默认值:7200 秒(2 小时)
sysctl net.ipv4.tcp_keepalive_time
  1. tcp_keepalive_intvl:
    • 含义:两次 KeepAlive 消息之间的时间间隔。
    • 默认值:75 秒
sysctl net.ipv4.tcp_keepalive_intvl
  1. tcp_keepalive_probes:
    • 含义:在放弃连接之前发送的 KeepAlive 消息的次数。
    • 默认值:9 次
sysctl net.ipv4.tcp_keepalive_probes

这些参数的配置可以通过修改 /etc/sysctl.conf 文件来实现。例如,要将 tcp_keepalive_time 设置为 600 秒,可以在 /etc/sysctl.conf 中添加如下行:

net.ipv4.tcp_keepalive_time = 600

然后,可以运行以下命令使更改生效:

sysctl -p

请注意,修改这些参数可能会对系统的整体性能产生影响,因此在进行更改之前,请仔细了解每个参数的含义以及它们的默认值。

查看当前系统的TCP KeepAlive参数

sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_probes
sysctl net.ipv4.tcp_keepalive_intvl

修改TCP KeepAlive参数

sysctl net.ipv4.tcp_keepalive_time=3600

在这里插入图片描述
在这里插入图片描述

三、C语言实现TCP KeepAlive功能

对于Socket而言,可以在程序中通过socket选项开启TCP KeepAlive功能,并配置参数。

对应的Socket选项分别为 SO_KEEPALIVE 、TCP_KEEPIDLE 、 TCP_KEEPCNT 、 TCP_KEEPINTVL 。

关于 setsockopt 函数的参数和相应的套接字选项以及协议的对应关系,以及对应的描述。

setsockopt 参数描述对应的 Socket Level(SOL)对应的协议(Protocol)
SO_KEEPALIVE启用或禁用在套接字上发送保持活动消息。保持活动消息用于检测连接是否仍然活动。SOL_SOCKETIPPROTO_TCP
TCP_KEEPIDLE指定连接在 TCP 开始发送保持活动探测之前必须空闲的时间(以秒为单位)。SOL_TCPIPPROTO_TCP
TCP_KEEPCNT指定在将连接视为中断之前 TCP 应发送的最大保持活动探测次数。SOL_TCPIPPROTO_TCP
TCP_KEEPINTVL指定个别保持活动探测之间的时间(以秒为单位)。SOL_TCPIPPROTO_TCP
IPPROTO_TCPTCP(传输控制协议)的协议级别。用于设置特定于 TCP 的套接字选项。N/AIPPROTO_TCP

setsockopt 中参数与套接字选项和协议的关系。

Socket 选项和 TCP 参数

选项/参数描述
SO_KEEPALIVE启用或禁用在套接字上发送保持活动消息。保持活动消息用于检测连接是否仍然活动。
TCP_KEEPIDLE指定连接在 TCP 开始发送保持活动探测之前必须空闲的时间(以秒为单位)。
TCP_KEEPCNT指定在将连接视为中断之前 TCP 应发送的最大保持活动探测次数。
TCP_KEEPINTVL指定个别保持活动探测之间的时间(以秒为单位)。
# 四、setsockopt用于设置套接字选项的系统调用

setsockopt 函数是用于设置套接字选项的系统调用。下面是 setsockopt 函数的基本格式:

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

其中:

  • sockfd:套接字文件描述符。
  • level:选项所在的协议层。常用的有 SOL_SOCKET(用于通用套接字选项)和特定的协议层如 IPPROTO_TCP(用于 TCP 协议选项)。
  • optname:选项名称,用于指定需要设置的具体选项。
  • optval:指向包含选项值的缓冲区的指针。
  • optlen:指定 optval 缓冲区的大小。

下面是一些常见的套接字选项和它们的作用:

  1. SO_KEEPALIVE(SOL_SOCKET)

    • 描述:启用或禁用在套接字上发送保持活动消息。保持活动消息用于检测连接是否仍然活动。
  2. TCP_KEEPIDLE(SOL_TCP)

    • 描述:指定连接在 TCP 开始发送保持活动探测之前必须空闲的时间(以秒为单位)。
  3. TCP_KEEPCNT(SOL_TCP)

    • 描述:指定在将连接视为中断之前 TCP 应发送的最大保持活动探测次数。
  4. TCP_KEEPINTVL(SOL_TCP)

    • 描述:指定个别保持活动探测之间的时间(以秒为单位)。
  5. IPPROTO_TCP

    • 描述:TCP(传输控制协议)的协议级别。用于设置特定于 TCP 的套接字选项。

这些选项可用于控制套接字的行为,如保持连接活动、调整发送和接收缓冲区的大小等。在使用时,请注意确保提供正确的协议级别和选项名称。

int keepalive = 1; // 开启TCP KeepAlive功能
int keepidle = 5; // tcp_keepalive_time 3s内没收到数据开始发送心跳包
int keepcnt = 3; // tcp_keepalive_probes 每次发送心跳包的时间间隔,单位秒
int keepintvl = 3; // tcp_keepalive_intvl 每3s发送一次心跳包

setsockopt(client_socketfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepidle, sizeof(keepidle));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof(keepcnt));
setsockopt(client_socketfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof(keepintvl));

五、代码实现

mysocket.h

#ifndef __MYSOCKET__H
#define __MYSOCKET__H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <unistd.h>

#define IPADDR "192.168.1.254" //填写自己实际的ip地址
#define IPPORT "8192"
#define BUF_SIZE 6

int socket_init(const char *ipaddr, const char *port);

#endif

mysocket.c

#include "mysocket.h"

int socket_init(const char *ipaddr, const char *port)
{
	int server_fd = -1;
	struct sockaddr_in server_addr;

	memset(&server_addr, 0, sizeof(struct sockaddr_in));

	// 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);

    if (server_fd == -1) {
        perror("Socket creation failed");
        return -1;
    }

    // 设置服务器地址结构
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
//  server_addr.sin_addr.s_addr = INADDR_ANY;
    inet_aton(ipaddr, &server_addr.sin_addr);

    // 绑定服务器套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Binding failed");
        return -1;
    }

    // 监听传入的连接请求
    if (listen(server_fd, 1) == 0) {	//只监听1个连接,排队扔垃圾
        printf("Listening for incoming connections...\n");
    } else {
        perror("Listening failed");
        return -1;
    }

	return server_fd;
}

main.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <wiringPi.h>
#include <pthread.h>

#include "uartTool.h"
#include "garbage.h"
#include "pwm.h"
#include "myoled.h"
#include "mysocket.h"

int serial_fd = -1; // 串口文件描述符
pthread_cond_t cond; // 条件变量,用于线程之间的条件同步
pthread_mutex_t mutex; // 互斥锁,用于线程之间的互斥访问

 // 判断进程是否在运行
static int detect_process(const char * process_name)
{
	int n = -1; // 存储进程PID,默认为-1
	FILE *strm;
	char buf[128] = {0}; // 缓冲区
	
	// 构造命令字符串,通过ps命令查找进程
	sprintf(buf, "ps -ax | grep %s|grep -v grep", process_name);
	// 使用popen执行命令并读取输出
	if ((strm = popen(buf, "r")) != NULL) {
		if (fgets(buf, sizeof(buf), strm) != NULL) {
			printf("buf = %s\n", buf); 	//打印缓存区的内容
			n = atoi(buf); 				// 将进程ID字符串转换为整数
			printf("n = %d\n", n); 		// 打印下进程的PID
		}
	}
	else {
		return -1; // popen失败
	}	
	pclose(strm); // 关闭popen打开的文件流

	return n;
}

// 获取语音线程
void *pget_voice(void *arg)
{
	unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};
	int len = 0;

	printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
	// 串口未打开,退出线程
	if (-1 == serial_fd) {
		printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);
		pthread_exit(0);
	}

	printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
	// 循环读取串口数据
	while (1) {
		len = my_serialGetstring(serial_fd, buffer);

		printf("%s|%s|%d, len = %d\n", __FILE__, __func__, __LINE__, len);
		// 检测到特定数据,发出信号唤醒其他线程
		if (len > 0 && buffer[2] == 0x46) {
			printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
			pthread_mutex_lock(&mutex);
			buffer[2] = 0x00;
			pthread_cond_signal(&cond);
			pthread_mutex_unlock(&mutex);

			system(WGET_CMD);
		}
	}
	pthread_exit(0);
}

// 发送语音线程
void *psend_voice(void *arg)
{
	pthread_detach(pthread_self());
	unsigned char *buffer = (unsigned char *)arg;

	// 串口未打开,退出线程
	if (-1 == serial_fd) {
		printf("%s|%s|%d: open serial failed\n", __FILE__, __func__, __LINE__);
		pthread_exit(0);
	}

	// buffer不为空时,通过串口发送数据(分类结果)
	if (NULL != buffer) {
		my_serialSendstring(serial_fd, buffer, 6);
	}
	pthread_exit(0);
}

// 控制垃圾桶线程
void *popen_trash_can(void *arg)
{
	pthread_detach(pthread_self());
	unsigned char *buffer = (unsigned char *)arg;

	// 根据垃圾类型控制PWM
	if (buffer[2] == 0x43) {		// 可回收垃圾
		printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);
		pwm_write(PWM_RECOVERABLE_GARBAGE);
		delay(2000);
		pwm_stop(PWM_RECOVERABLE_GARBAGE);
	}
	else if (buffer[2] == 0x41) {	// 干垃圾
		printf("%s|%s|%d: buffer[2] = 0x%x\n", __FILE__, __func__, __LINE__, buffer[2]);
		pwm_write(PWM_GARBAGE);
		delay(2000);
		pwm_stop(PWM_GARBAGE);
	}
	else if (buffer[2] == 0x42) {	// 湿垃圾
        printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);
        pwm_write(PWM_WET_GARBAGE);
        delay(2000);
        pwm_stop(PWM_WET_GARBAGE);
    }
    else if (buffer[2] == 0x44) {	// 有害垃圾
        printf("%s|%s|%d: buffer[2]=0x%x\n", __FILE__, __func__, __LINE__,buffer[2]);
        pwm_stop(PWM_HAZARDOUS_GARBAGE);
        delay(2000);
        pwm_write(PWM_HAZARDOUS_GARBAGE);
    }
	pthread_exit(0);
}

// 在线程中显示 OLED
void *poled_show(void *arg)
{
	// 分离线程,使其在退出时能够自动释放资源
	pthread_detach(pthread_self());
	// 初始化 OLED
	myoled_init();
	// 在 OLED 上显示垃圾分类结果
	oled_show(arg);
	// 退出线程	
	pthread_exit(0);
}

// 垃圾分类线程
void *pcategory(void *arg)
{
	unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};
	char *category = NULL;
	pthread_t send_voice_tid, trash_tid, oled_tid;

	while (1) {
		printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond, &mutex);
		pthread_mutex_unlock(&mutex);

		printf("%s|%s|%d: \n", __FILE__, __func__, __LINE__);
		buffer[2] = 0x00;

		// 在执行wget命令之前添加调试输出
		printf("Executing wget command...\n");
		// 使用系统命令拍照
		system(WGET_CMD);
		// 在执行wget命令之后添加调试输出
		printf("Wget command executed.\n");
		
		// 判断垃圾种类
		if (0 == access(GARBAGE_FILE, F_OK)) {
			category = garbage_category(category);
			if (strstr(category, "干垃圾")) {
				buffer[2] = 0x41;
			}
			else if (strstr(category, "湿垃圾")) {
				buffer[2] = 0x42;
			}
			else if (strstr(category, "可回收垃圾")) {
				buffer[2] = 0x43;
			}
			else if (strstr(category, "有害垃圾")) {
				buffer[2] = 0x44;
			}
			else {
				buffer[2] = 0x45; // 未识别到垃圾类型
			}
		}
		else {
			buffer[2] = 0x45; // 识别失败
		}
		// 开垃圾桶开关
		pthread_create(&trash_tid, NULL, psend_voice, (void *)buffer);

		// 开语音播报线程
		pthread_create(&send_voice_tid, NULL, popen_trash_can, (void *)buffer);

        //oled显示线程
        pthread_create(&oled_tid, NULL, poled_show, (void *)buffer);

		// buffer[2] = 0x00;
		// 删除拍照文件
		remove(GARBAGE_FILE); 
	}
	pthread_exit(0);
}

void *pget_socket(void *arg)
{
	int server_fd = -1;				// 服务器 socket 文件描述符
	int client_fd = -1;				// 客户端 socket 文件描述符
	char buffer[6];					// 用于存储接收到的数据的缓冲区
	int nread = -1;					// 接收到的数据长度
	struct sockaddr_in client_addr;	// 客户端地址信息结构体
	int clen = sizeof(struct sockaddr_in);

	memset(&client_addr, 0, sizeof(struct sockaddr_in));

	// 初始化服务器 socket
	server_fd = socket_init(IPADDR, IPPORT);
	printf("%s|%s|%d:server_fd = %d\n", __FILE__, __func__, __LINE__, server_fd);

	if (-1 == server_fd) {
		pthread_exit(0); // 初始化失败,退出线程
	}
	sleep(3); // 等待 socket 初始化完成

	while (1) {
		// 接受客户端连接
		client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &clen);

		// 配置 TCP KeepAlive 参数
		int keepalive = 1; 			// 开启TCP KeepAlive功能
		int keepidle = 5; 			// tcp_keepalive_time 3s内没收到数据开始发送心跳包
		int keepcnt = 3; 			// tcp_keepalive_probes 每次发送心跳包的时间间隔,单位秒
		int keepintvl = 3;			// tcp_keepalive_intvl 每3s发送一次心跳包

		setsockopt(client_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive));
		setsockopt(client_fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepidle, sizeof(keepidle));
		setsockopt(client_fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcnt, sizeof(keepcnt));
		setsockopt(client_fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepintvl, sizeof(keepintvl));
		
		printf("%s|%s|%d: Accept a connection from %s:%d\n", __FILE__, __func__, __LINE__, 
		inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

		if (client_fd == -1) {
			perror("accept");
			continue; // 如果接受连接失败,继续下一次循环
		}

		while (1) 
		{
			memset(buffer, 0, sizeof(buffer));
			 // 从客户端接收数据
			nread = recv(client_fd, buffer, sizeof(buffer), 0); //n_read = read(c_fd, buffer, sizeof(buffer));
			printf("%s|%s|%d:nread = %d, buffer = %s\n", __FILE__, __func__, __LINE__, nread, buffer);

			// 根据接收到的数据执行相应的操作
			if (nread > 0) 
			{
				if (strstr(buffer, "open")) {
					pthread_mutex_lock(&mutex);
					pthread_cond_signal(&cond);
					pthread_mutex_unlock(&mutex);
				}
				if (strstr(buffer, "k1")) {
                    pwm_write(PWM_RECOVERABLE_GARBAGE);
                    delay(2000);
                    pwm_stop(PWM_RECOVERABLE_GARBAGE);
                }
                if (strstr(buffer, "k2")) {
                    pwm_write(PWM_GARBAGE);
                    delay(2000);
                    pwm_stop(PWM_GARBAGE);
                }
                if (strstr(buffer, "k3")) {
                    pwm_write(PWM_WET_GARBAGE);
                    delay(2000);
                    pwm_stop(PWM_WET_GARBAGE);
                }
                if (strstr(buffer, "k4")) {
                    pwm_stop(PWM_HAZARDOUS_GARBAGE);
                    delay(2000);
                    pwm_write(PWM_HAZARDOUS_GARBAGE);
                }
			}
			else if (0 == nread || -1 == nread) {
				break; // 如果接收到的数据长度为 0 或者出错,跳出循环
			}
		}
		close(client_fd); // 关闭客户端连接
	}
	pthread_exit(0); // 退出线程
}

int main(int argc, char *argv[])
{
	int ret = -1;
	int len = 0;
	char *category = NULL;
	pthread_t get_voice_tid, category_tid, get_socket_tid;

	wiringPiSetup();

	// 初始化串口和垃圾分类模块
	garbage_init ();

	// 用于判断mjpg_streamer服务是否已经启动
	ret = detect_process ("mjpg_streamer");

	if (-1 == ret) {
		printf("detect process failed\n");
        goto END;
	}
	
	// 打开串口
	serial_fd = my_serialOpen (SERIAL_DEV, BAUD);

	if (-1 == serial_fd) {
		printf("open serial failed\n");
		goto END;
	}

	// 开语音线程
	printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
	pthread_create(&get_voice_tid, NULL, pget_voice, NULL);

	// 开网络线程
	printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
	pthread_create(&get_socket_tid, NULL, pget_socket, NULL);

	// 开阿里云交互线程
	printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
	pthread_create(&category_tid, NULL, pcategory, NULL);

	// 创建互斥锁和条件变量
	pthread_join(get_voice_tid, NULL);
	pthread_join(category_tid, NULL);
	pthread_join(get_socket_tid, NULL);

	// 销毁互斥锁和条件变量
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);

	// 关闭串口
	close(serial_fd);
END:
	// 释放垃圾分类资源
	garbage_final();

	return 0;
}

六、待定

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

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

相关文章

为什么别人能做好CSGO游戏搬砖,而你不能?

CSGO搬砖日常出货更新 做Steam游戏搬砖的门槛很低&#xff0c;以至于这两年不断有小白涌入市场&#xff0c;想要在饰品市场中分一杯羹。这个项目是很简单&#xff0c;但想要站稳脚跟&#xff0c;有稳定收入的第一步就得搞清楚项目逻辑。 首先你得搞清楚&#xff0c;steam搬砖盈…

【MySQL系列】PolarDB入门使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码

基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于孔雀优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

巧妙之中见真章:深入解析常用的创建型设计模式

设计模式之创建型设计模式详解 一、设计模式是什么&#xff1f;二、模板方法2.1、代码结构2.2、符合的设计原则2.3、如何扩展代码2.4、小结 三、观察者模式3.1、代码结构3.2、符合的设计原则3.3、如何扩展代码3.4、小结 四、策略模式4.1、代码结构4.2、符合的设计原则4.3、如何…

计算机视觉面试题-01

计算机视觉面试通常涉及广泛的主题&#xff0c;包括图像处理、深度学习、目标检测、特征提取、图像分类等。以下是一些可能在计算机视觉面试中遇到的常见问题&#xff1a; 图像处理和计算机视觉基础 图像是如何表示的&#xff1f; 图像在计算机中可以通过不同的表示方法&…

Leetcode—28.找出字符串中第一个匹配项的下标【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—28.找出字符串中第一个匹配项的下标 实现代码 int strStr(char* haystack, char* needle) {int len1 strlen(haystack);int len2 strlen(needle);int idx -1;int i 0;while(i < len1 - len2) {if(strncmp(haystac…

尺度为什么是sigma?

我们先看中值滤波和均值滤波。 以前&#xff0c;我认为是一样的&#xff0c;没有区分过。 他们说&#xff0c;均值滤波有使图像模糊的效果。 中值滤波有使图像去椒盐的效果。为什么不同呢&#xff1f;试了一下&#xff0c;果然不同&#xff0c;然后追踪了一下定义。 12345&…

从程序员到解决方案工程师:一次跨界的职场冒险

在科技行业里&#xff0c;程序员和解决方案工程师是两个非常常见的职业。虽然这两个职业都需要一定的行业理解和问题解决能力&#xff0c;但它们的工作内容和职责却有很大的不同。 那么&#xff0c;如果一名程序员决定转行做一名解决方案工程师&#xff0c;他会经历怎样的体验…

QXDM Filter使用指南

QXDM Filter使用指南 1. QXDM简介2 如何制作和导入Filter2.1 制作Filter2.1.1 制作Windows环境下Filter2.1.2 制作Linux环境下Filter 2.2 Windows环境下导入Filter 3 Filter配置3.1 注册拨号问题3.1.1 LOG Packets(OTA)3.1.2 LOG Packets3.1.3 Event Reports3.1.4 Message Pack…

Java网络爬虫实战

List item 文章目录 ⭐️写在前面的话⭐️&#x1f4cc;What is it?分类网络爬虫按照系统结构和实现技术&#xff0c;大致可以分为以下几种类型&#xff1a;通用网络爬虫&#xff08;General Purpose Web Crawler&#xff09;、聚焦网络爬虫&#xff08;Focused Web Crawler&a…

关于python中的nonlocal关键字

如果在函数的子函数中需要调用外部变量&#xff0c;一般会看见一个nonlocal声明&#xff0c;类似下面这种&#xff1a; def outer_function():x 10def inner_function():nonlocal xx 1print(x)inner_function()outer_function()在这个例子中&#xff0c;inner_function 引用…

AR眼镜双目光波导/主板硬件方案

AR(增强现实)技术的发展离不开光学元件&#xff0c;而在其中&#xff0c;光波导和Micro OLED被视为AR眼镜光学方案的黄金搭档。光学元件在AR行业中扮演着核心角色&#xff0c;其成本高昂且直接影响用户体验的亮度、清晰度和大小等因素。AR眼镜的硬件成本中&#xff0c;光机部分…

Postman如何使用(二):Postman Collection的创建/使用/导出分享等

一、什么是Postman Collection&#xff1f; Postman Collection是可让您将各个请求分组在一起。 您可以将这些请求组织到文件夹中。中文经常将collection翻译成收藏夹。如果再下文中看到这样的翻译不要觉得意外。Postman Collection会使你的工作效率更上一层楼。Postman Colle…

浅谈现代化城市建设中智慧消防的研究与应用

安科瑞 华楠 【摘要】随着城市现代化发展&#xff0c;城市居住密度愈来愈大&#xff0c;城市建筑结构复杂多样化&#xff0c;高层建筑火灾发生率在不断地升高。对现代化城市面临的消防问题展开讨论&#xff0c;针对智慧消防在现代化城市建设中的现状进行了分析&#xff0c;并提…

肾合胶囊 | 修行人追求的“长生不老”,其实就是一个“增阳消阴”的过程!

关于生命的问题&#xff0c;在人们面前有两条路任你选择。 人的生命活动靠精气来维持&#xff0c;善于保养精气者长生&#xff0c;否则就是短寿。 长生或短寿&#xff0c;只能由你自己选择。 其实要想长生并不是很难的事情&#xff0c;长生药就在你自己身上&#xff0c;只不…

YOLO目标检测——背包检测数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;各种背包检测数据集说明&#xff1a;背包检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富标签说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(json)和yolo(txt)三种格式标签…

SpringBoot 2 系列停止维护,Java8 党何去何从?

SpringBoot 2.x 版本正式停止更新维护&#xff0c;官方将不再提供对 JDK8 版本的支持 SpringBoot Logo 版本的新特性 3.2 版本正式发布&#xff0c;亮点包括&#xff1a; 支持 JDK17、JDK21 版本 对虚拟线程的完整支持 JVM Checkpoint Restore&#xff08;Project CRaC&…

OSG粒子系统与阴影-自定义粒子系统示例<1>(4)

自定义粒子系统示例(一) 自定义粒子系统示例(一)的代码如程序清单11-5所示&#xff1a; /* 自定义粒子系统示例1 */ void particleSystem_11_5(const string &strDataFolder) {osg::ref_ptr<osgViewer::Viewer> viewer new osgViewer::Viewer();osg::ref_ptr<os…

【每日一题】1457. 二叉树中的伪回文路径-2023.11.25

题目&#xff1a; 1457. 二叉树中的伪回文路径 给你一棵二叉树&#xff0c;每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的&#xff0c;当它满足&#xff1a;路径经过的所有节点值的排列中&#xff0c;存在一个回文序列。 请你返回从根到叶子节点的所有路…

illuminate/database 使用 四

文档&#xff1a;Hyperf Database: Getting Started - Laravel 10.x - The PHP Framework For Web Artisans 因为hyperf使用illuminate/database&#xff0c;所以按照文章&#xff0c;看illuminate/database代码实现。 一、读写分离 根据文档读写的host可以分开。设置读写分…