【阿里云】图像识别 智能分类识别 增加垃圾桶开关盖功能点和OLED显示功能点(二)

news2024/12/28 20:32:55

一、增加垃圾桶开关盖功能

  • 环境准备

二、PWM 频率的公式
三、pthread_detach分离线程,使其在退出时能够自动释放资源
四、具体代码实现

  • 图像识别数据及调试信息
  • wget-log打印日志文件

五、增加OLED显示功能
六、功能点实现语音交互视频

一、增加垃圾桶开关盖功能

实现功能:使用语音模块和摄像头在香橙派上做垃圾智能分类识别, 同时根据识别结果开关不同的垃圾桶的盖子。

环境准备

在《语音模块和阿里云图像识别结合》搭建环境的基础上, 接上用于开关盖的舵机(舵机模块可以直接粘在垃圾桶内侧),当前代码里仅用了2个舵机用于示例代码的编写,可以自行多购买3个垃圾桶和舵机用于区分4垃圾类型,接线位置如下:
在这里插入图片描述

实物图

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

二、PWM 频率的公式

这个 PWM 频率的公式可以更详细地表示为:

P W M f r e q = 1 × 1 0 6 pulse-width × range   \\{PWMfreq} = \frac{1 \times 10^6}{\text{pulse-width} \times \text{range}} \ PWMfreq=pulse-width×range1×106 

其中:

  • (\text{PWMfreq}) 是 PWM 的频率(赫兹)。
  • (1 \times 10^6) 是为了将频率从赫兹(Hz)转换为微秒(μs)。
  • (\text{pulse-width}) 是每个 PWM 脉冲的宽度(微秒)。
  • (\text{range}) 是 PWM 的范围,即 PWM 值的最大范围。

这个公式的基本思想是,PWM 的频率与脉冲宽度和范围有关。脉冲宽度表示每个 PWM 脉冲的持续时间,而范围表示 PWM 值的最大范围。通过调整这两个参数,可以控制 PWM 的频率。

三、pthread_detach分离线程,使其在退出时能够自动释放资源

pthread_detach 函数是 POSIX 线程库提供的一个函数,用于将一个线程标记为可被回收的。标记为可被回收的线程在退出时会自动释放其占用的系统资源,无需等待其他线程调用 pthread_join

具体来说,当一个线程被标记为可被回收时,其退出状态会自动被收回。这对于那些不需要其他线程等待其结束的线程是有用的,因为它允许主线程或其他线程继续执行而无需等待这个线程的完成。

#include <pthread.h>

int pthread_detach(pthread_t thread);
  • pthread_detach 的参数是一个线程标识符(pthread_t 类型的变量),它表示要被标记为可被回收的线程。
  • 如果线程标识符为 thread 的线程处于 joinable 状态,那么它会被标记为可被回收,并且在线程退出时,其资源将被自动释放。
  • 如果线程已经处于 detached 状态,或者线程标识符不对应一个现存的线程,pthread_detach 函数将返回适当的错误码。

示例用法:

#include <pthread.h>

void *thread_function(void *arg) {
    // 线程的执行体
    // ...
    return NULL;
}

int main() {
    pthread_t my_thread;

    // 创建线程
    if (pthread_create(&my_thread, NULL, thread_function, NULL) != 0) {
        // 线程创建失败处理
        return 1;
    }

    // 将线程标记为可被回收
    if (pthread_detach(my_thread) != 0) {
        // 线程标记失败处理
        return 1;
    }

    // 主线程继续执行而不用等待子线程的结束
    // ...

    return 0;
}

在这个例子中,my_thread 线程被创建后立即被标记为可被回收,主线程可以继续执行而不用等待 my_thread 线程的完成。

总结:
你可以将这种机制称为“分离线程”或“分离父子线程”。当你将一个线程标记为可被回收,这个线程就不再和主线程形成关联,主线程不需要显式地等待它的结束。这样的线程就像“自洁”一样,它在结束时会自动释放资源。

这种机制对于那些主线程不关心其返回值,也不需要等待其结束的辅助线程是非常有用的。这样,主线程和辅助线程可以并行执行,提高了程序的性能。

四、具体代码实现

  1. 增加用于实现开光盖(驱动舵机)的源码文件(pwm.c):
#include <wiringPi.h>
#include <softPwm.h>
#include "pwm.h"

// 根据PWM 频率公式:PWMfreq = 1 x 10^6 / (100 x range) 。
// 要得到PWM频率为50Hz,则range设置周期分为200步,周期20ms,控制精度相比硬件PWM较低。

// 设置指定PWM引脚的输出,实现模拟PWM
void pwm_write(int pwm_pin)
{
	pinMode(pwm_pin, OUTPUT);
	softPwmCreate(pwm_pin, 0, 200);	// 创建软件PWM,初始占空比为0%,范围为0到200
	softPwmWrite(pwm_pin, 10);		// 设置占空比为10%	45度
	delay(1000);					// 延时1秒
	softPwmStop(pwm_pin);			// 停止软件PWM
}

// 停止指定PWM引脚的输出
void pwm_stop(int pwm_pin)
{
	pinMode(pwm_pin, OUTPUT);
	softPwmCreate(pwm_pin, 0, 200);	// 创建软件PWM,初始占空比为0%,范围为0到200
	softPwmWrite(pwm_pin, 5);		// 设置占空比为5%	0度
	delay(1000);					// 延时1秒
	softPwmStop(pwm_pin);			// 停止软件PWM
}
  1. pwm.h代码:
#ifndef __PWM__H
#define __PWM__H

#define PWM_GARBAGE 7				// 干垃圾
#define PWM_RECOVERABLE_GARBAGE 5	// 可回收垃圾
#define PWM_WET_GARBAGE 8			// 湿垃圾
#define PWM_HAZARDOUS_GARBAGE 9		// 有害垃圾

void pwm_write(int pwm_pin);		// 设置指定PWM引脚的输出
void pwm_stop(int pwm_pin);			// 停止指定PWM引脚的输出

#endif
  1. 修改main.c代码,调整整体main函数的代码架构,利用多线程实现具体的功能(用到了线程里的条件变量控制线程间的数据同步)
#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"

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

// 垃圾分类线程
void *pcategory(void *arg)
{
	unsigned char buffer[6] = {0xAA, 0x55, 0x00, 0x00, 0X55, 0xAA};
	char *category = NULL;
	pthread_t send_voice_tid, trash_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);
		// buffer[2] = 0x00;
		// 删除拍照文件
		remove(GARBAGE_FILE); 
	}
	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;

	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(&category_tid, NULL, pcategory, NULL);

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

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

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

	return 0;
}

图像识别数据及调试信息

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

wget-log打印日志文件

在这里插入图片描述
wget-log 文件名通常是 wget 命令行工具的默认日志文件名,用于记录 wget 下载命令执行过程中的信息、警告和错误。wget 是一个用于在命令行中下载文件的工具,而 wget-log 文件则用于记录执行 wget 命令时产生的输出。

如果你在使用类似如下的 wget 命令:

wget [URL]

wget 默认会将日志输出到 wget-log 文件中。如果你希望更改日志文件的名称,可以使用 -o 选项,例如:

wget -o mylog.txt [URL]

上述命令将日志输出到名为 mylog.txt 的文件中。因此,wget-log 文件的生成通常取决于 wget 命令的使用方式。

阿里云的相关操作(比如通过 wget 下载文件)也可能产生 wget-log 文件,具体情况可能取决于你执行的命令和阿里云环境的设置。如果有特定的 wget 命令或阿里云操作,你可以提供更多的上下文,以便我更好地理解你的问题。

五、增加OLED显示功能

  1. 环境配置
cat /boot/orangepiEnv.txt
ls -a /dev/i2c-3

在这里插入图片描述

  1. 《OLED屏应用-IIC协议》直接添在garbage项目中添加2个OLED实现代码文件

myoled.h

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>

#include "oled.h"
#include "font.h"

#ifndef __MYOLED__H
#define __MYOLED__H

int myoled_init(void);
int oled_show(void *arg);

#endif

myoled.c:

#include "myoled.h"

#define FILENAME "/dev/i2c-3"

static struct display_info disp;

// 在 OLED 上显示垃圾分类结果
int oled_show(void *arg)
{
    unsigned char *buffer = (unsigned char *)arg;

    // 在 OLED 上显示提示信息
    oled_putstrto(&disp, 0, 9+1, "THis garbage is:");
    disp.font = font2;
    
    // 根据垃圾类型显示相应信息
    switch(buffer[2])
    {
        case 0x41:
            oled_putstrto(&disp, 0, 20, "Dry_garbage");
            break;
        case 0x42:
            oled_putstrto(&disp, 0, 20, "Wet_garbage");
            break;
        case 0x43:
            oled_putstrto(&disp, 0, 20, "Recycle_garbage");
            break;
        case 0x44:
            oled_putstrto(&disp, 0, 20, "Hazardous_garbage");
            break;
        case 0x45:
            oled_putstrto(&disp, 0, 20, "recognition failed");
            break;
    }
    disp.font = font2;

    // 发送显示缓冲区到 OLED
    oled_send_buffer(&disp);
    
    return 0;
}

// 初始化 OLED
int myoled_init(void)
{
    int e;
    disp.address = OLED_I2C_ADDR;
    disp.font = font2;

    // 打开 OLED 设备文件
    e = oled_open(&disp, FILENAME);
    // 初始化 OLED
    e = oled_init(&disp);

    return e;
}
  1. 然后修改下main.c文件, 增加OLED线程,用于显示识别后的垃圾类型:
#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"

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

int main(int argc, char *argv[])
{
	int ret = -1;
	int len = 0;
	char *category = NULL;
	pthread_t get_voice_tid, category_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(&category_tid, NULL, pcategory, NULL);

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

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

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

	return 0;
}

在这里插入图片描述

六、功能点实现语音交互视频

垃圾分类

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

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

相关文章

vue3(一)-基础入门

一、导入vue.js 1.可以借助 script 标签直接通过 CDN 来使用 Vue <!-- <script src"https://unpkg.com/vue3/dist/vue.global.js"></script> -->2.也可以下载vue.global.js文件并在本地导入 <script src"./lib/vue.global.js">&…

3、Qt使用windeploy工具打包可执行文件

新建一个文件夹&#xff0c;把要打包的可执行文件exe拷贝过来 点击输入框&#xff0c;复制一下文件夹路径 点击电脑左下角&#xff0c;找到Qt文件夹&#xff0c; 点击打开 “Qt 5.12.0 for Desktop” &#xff08;我安装的是Qt 5.12.0版本&#xff09; 输入“cd bin”&#xff…

转录组学习第5弹-比对参考基因组

比对参考基因组 在构建文库的过程中需要将DNA片段化&#xff0c;因此测序得到的序列只是基因组的部分序列。为了确定测序reads在基因组上的位置&#xff0c;需要将reads比对回参考基因组上&#xff0c;这个步骤叫做比对&#xff0c;即文献中所提到的alignment或mapping。包括基…

代码随想录算法训练营第30天|回溯总结 332. 重新安排行程

回溯是递归的副产品&#xff0c;只要有递归就会有回溯&#xff0c;所以回溯法也经常和二叉树遍历&#xff0c;深度优先搜索混在一起&#xff0c;因为这两种方式都是用了递归。 回溯法就是暴力搜索&#xff0c;并不是什么高效的算法&#xff0c;最多再剪枝一下。 回溯算法能解…

自动语音识别 支持86种语言 Dragon Professional 16 Crack

从个体从业者到全球组织&#xff0c;文档密集型行业的专业人士长期以来一直依靠 Dragon 语音识别来更快、更高效地创建高质量文档&#xff0c;减少管理开销&#xff0c;以便他们能够专注于客户。了解 Dragon Professional v16 如何通过单一解决方案提高标准&#xff0c;为各个业…

YB4556 28V、1A、单节、线性锂电池充电IC

YB4556 28V 、 1A 、单节、线性锂电池充电 IC 概述: YB4556H 是一款完整的采用恒定电流 / 恒定电压的高压、大电流、单节锂离子电池线性充电 IC。最高耐压可达 28V&#xff0c;6.5V 自动过压保护&#xff0c;充电电流可达 1A。由于采用了内部 PMOSFET 架构&#xff0c;加上防倒…

推荐6款本周 yyds 的开源项目

&#x1f525;&#x1f525;&#x1f525;本周GitHub项目圈选: 主要包含 链接管理、视频总结、有道音色情感合成、中文文本格式校正、GPT爬虫、深度学习推理 等热点项目。 1、Dub 一个开源的链接管理工具&#xff0c;可自定义域名将繁杂的长链接生成短链接&#xff0c;便于保…

云计算领域的第三代浪潮!

根据IDC不久前公布的数据&#xff0c;2023年上半年中国公有云服务整体市场规模(IaaS/PaaS/SaaS)为190.1亿美元&#xff0c;阿里云IaaS、PaaS市场份额分别为29.9%和27.9%&#xff0c;都远超第二名&#xff0c;是无可置疑的行业领头羊。 随着人工智能&#xff08;AI&#xff09;…

ADRC自抗扰控制原理

这里写目录标题 TD跟踪微分器ESONLSEF后续把公式的核心原理分析一下 参考链接&#xff1a;ADRC自抗扰控制&#xff0c;有手就行 ADRC是升级版的PID&#xff0c;由TD&#xff08;跟踪微分器&#xff09;&#xff0c;ESO&#xff08;扩张状态观测器&#xff09;&#xff0c;NLSEF…

C语言—sizeof和strlen的区别

sizeof和strlen的区别 1、两者无联系 2、 sizeof&#xff1a;计算数组&#xff0c;变量&#xff0c;类型所在空间的大小&#xff0c;单位是字节 strlen&#xff1a;求字符串的长度&#xff0c;\0之前的字符个数&#xff0c;只针对字符串求长度 3、sizeof是操作符 strlen是库…

现代图标集wxArtProvider发布 —— 发布于2023年11月21日

Perazz发布了wxMaterialDesignArtProvider&#xff0c;这是一个自定义的wxArtProvider类&#xff0c;从MaterialDesign、SimpleIcons、FontAwesome和FluentUI系统数据集中提供基于SVG的图标。所有这些数据集都有许可证&#xff08;MIT、CC BY 4.0、CC0 1.0、Apache 2.0&#xf…

PyQt6把QTDesigner生成的UI文件转成python源码,并运行

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计18条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

LeetCode二叉树小题目

Q1将有序数组转换为二叉搜索树 题目大致意思就是从一个数组建立平衡的二叉搜索树。由于数组以及进行了升序处理&#xff0c;我们只要考虑好怎么做到平衡的。平衡意味着左右子树的高度差不能大于1。由此我们可以想着是否能用类似二分递归来解决。 如果left>right,直接返回nul…

晶振为什么不能放置在PCB边缘

某行车记录仪&#xff0c;测试的时候要加一个外接适配器&#xff0c;在机器上电运行测试时发现辐射超标&#xff0c;具体频点是84MHz、144MHz、168MHz&#xff0c;需要分析其辐射超标产生的原因&#xff0c;并给出相应的对策。辐射测试数据如下&#xff1a; 图1&#xff1a;辐…

CDN的认识与绕过

CDN的认识与绕过 什么是CDN CDN的全称是Content Delivery Network&#xff0c;即内容分发网络。它依靠部署在各地的边缘服务器&#xff0c;通过中心平台的负载均衡、内容分发、调度等功能模块&#xff0c;使用户就近获取所需内容&#xff0c;降低网络拥塞&#xff0c;提高用户…

2023.11.25-电商项目建设业务学习1-指标,业务流程,核销

目录 1.指标分类(原子指标,派生指标,衍生指标) 2.一些业务名词 3.四大业务流程-销售需求 3.1-线上线下销售 3.2线上线下退款 4.四大业务流程-会员业务 5.四大业务流程-供应链业务 6.四大业务流程-商城业务 7.核销主题需求分析 1.指标分类(原子指标,派生指标,衍生指标) 原…

【Python自学】七个超强学习网站,你值得拥有!

学习Python最主要的还是要动手&#xff0c;去找一些自己感兴趣的脚本&#xff0c;代码去练习&#xff0c;练的越多&#xff0c;对于一些英语单词&#xff0c;特殊符号要比死记硬背要容易记得些。 以下这些网站&#xff0c;虽说不上全方位的满足你的需求&#xff0c;但是大部分也…

优秀的时间追踪软件Timemator for Mac轻松管理时间!

在现代社会&#xff0c;时间管理成为了我们工作和生活中的一大挑战。如果你经常感到时间不够用&#xff0c;无法高效地完成任务&#xff0c;那么Timemator for Mac将成为你的得力助手。 Timemator for Mac是一款出色的时间追踪软件&#xff0c;它可以帮助你精确记录和管理你的…

Vatee万腾独特科技力量的前沿探索:Vatee的数字化奇点

在当今科技的浪潮中&#xff0c;Vatee万腾以其独特的科技力量成为前沿探索的引领者&#xff0c;正迎来数字化奇点的新时代。Vatee万腾不仅仅是一家科技公司&#xff0c;更是一支探索未知领域、开创数字时代新局面的先锋力量。 Vatee万腾的数字化奇点体现在其对前沿技术的深刻理…

BART - 磁共振重建库 linux系统安装 MATLAB 使用

本文主要介绍如何在linux系统中安装伯克利大学的磁共振重建库BART 和在matlab中的配置使用。 安装必要的库 (linux 命令行) $ sudo apt-get install make gcc libfftw3-dev liblapacke-dev libpng-dev libopenblas-dev 下载编译BART 文件 (官网链接:BART Toolbox) 命令行下…