【基于树莓派400的图像识别归类&运动检测&模拟信号处理系统第五帖】结项&文档-11.0...
[复制链接]
本帖最后由 donatello1996 于 2022-11-3 14:06 编辑
【基于树莓派400的图像识别归类&运动检测&模拟信号处理系统第五帖】结项&文档-11.03更新
作者:李工
论坛昵称:donatello1996
作品简介
本系统以树莓派400卡片电脑为核心,连接I2C传感器MPU6050、BMP280、AHT20,连接USB UVC高清免驱摄像头,具有图像识别归类、运动检测、模拟信号处理、信号峰值检测、信号峰值统计,MP3播放器等功能,是一个泛用性多功能项目,其中的某一项或多项功能可以根据市面上的嵌入式项目需求进行组合并深入开发,充分利用了树莓派400 CPU信号运算性能,图形化显示性能。
本报告包含系统硬件框图及介绍,程序架构框图及介绍,运行效果,不包含源代码,源代码是以tar压缩包的形式单独发送到得捷官方主办方的。
系统硬件框图
硬件框图如下,硬件功能非常简单,只用到了USB Host接口接UVC摄像头,I2C1接口接三个I2C传感器,HDMI接口接显示屏用于QT界面显示/MP3音乐播放,以太网/WIFI用于搭建基于TCP的HTTP WEB服务器。如下为实拍图:
系统软件框图(进程/工程架构框图)
项目分为两个进程(工程),分别为main工程及qtproj工程,其中main工程只负责摄像头采集显示,tflite人工智能图像识别归类;qtproj工程只负责I2C数据采集图形化显示,MP3播放器,信号频率傅里叶变换,信号峰值检测,信号峰值统计。
main工程:
pthread_mutex_init(&pmt , NULL);
pthread_create(&tid_grab_mjpeg , NULL , Thread_V4l2_Grab_Mjpeg , NULL);
pthread_detach(tid_grab_mjpeg);
pthread_create(&tid_tcp_web_recv , NULL , Thread_TCP_Web_Recv , NULL);
pthread_create(&tid_tcp_web_recv_tflite , NULL , Thread_TCP_Web_Recv_Tflite , NULL);
// pthread_create(&tid_tcp_web_send , NULL , Thread_TCP_Web_Send , NULL);
//pthread_create(&tid_tcp_web_send , NULL , Thread_TCP_Web_Send_Only_MJPEG , NULL);
pthread_create(&tid_tflite , NULL , Thread_Tflite , NULL);
pthread_create(&tid_tcp_web_send , NULL , Thread_TCP_Web_Send_Only_JPEG_File , NULL);
pthread_create(&tid_tcp_web_send_tflite , NULL , Thread_TCP_Web_Send_Only_JPEG_File_Tflite , NULL);
qtproj工程:
pthread_create(&tid_line_chart , nullptr , Thread_Line_Chart , this);
connect(this , SIGNAL(Signal_Raw_data()) , this , SLOT(Raw_data_CounterUpdate_Line_Chart()));
connect(this , SIGNAL(Signal_Raw_data_Collection()) , this , SLOT(Raw_data_Collection_CounterUpdate_Line_Chart()));
各部分功能说明
软件功能设计
我的这个作品一共有9个功能:
-前两个功能,摄像头图像采集并显示,摄像头图像人工智能识别物体并归类,这两个功能都做在了HTTP WEB服务器里面,树莓派400运行C++进程main之后,可通过树莓派的
IP:50011和IP:50012这两个地址:端口来访问原始摄像头图像和经过人工智能识别物体并归类的摄像头图像,使用jpg图像编解码软件库需要安装libjpeg62-turbo:
源码如下:
摄像头抓取图像线程:
void * Thread_V4l2_Grab_Mjpeg(void *arg)
{
pic_data pic_temp;
while(1)
{
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(fd_video , VIDIOC_STREAMON , &type) < 0)
{
printf("Unable to start capture.\n");
break;
}
struct v4l2_buffer buff;
buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buff.memory = V4L2_MEMORY_MMAP;
if(ioctl(fd_video , VIDIOC_DQBUF, &buff) < 0)
{
printf("camera VIDIOC_DQBUF Failed.\n");
usleep(1000*1000);
break;
}
pthread_mutex_lock(&pmt);
memcpy(pic_tmpbuffer , pic.tmpbuffer , buff.bytesused);
//pic_tmpbuffer = pic.tmpbuffer;
pic.tmpbytesused = buff.bytesused;
pic_tmpbytesused = pic.tmpbytesused;
//if(build_file == true)
//{
int jpg_fd = open(MJPEG_FILE_NAME , O_RDWR | O_CREAT , 00700);
if(jpg_fd == -1)
{
printf("open ipg Failed!\n ");
break ;
}
int writesize = write(jpg_fd , pic.tmpbuffer , pic.tmpbytesused);
printf("Write successfully size : %d\n" , writesize);
close(jpg_fd);
//}
pthread_cond_broadcast(&pct);
pthread_mutex_unlock(&pmt);
printf("pic.tmpbytesused size : %d\n",pic.tmpbytesused);
if(ioctl(fd_video , VIDIOC_QBUF, &buff) < 0)
{
printf("camera VIDIOC_QBUF Failed.\n");
usleep(1000*1000);
break;
}
}
}
对图像进行人工智能处理的线程:
void * Thread_Tflite(void *arg)
{
clock_t start, finish;
double totaltime;
//VideoCapture cap(0);
//cap.set(CAP_PROP_FRAME_WIDTH, inpWidth);
//cap.set(CAP_PROP_FRAME_HEIGHT, inpHeight);
String weights = "./model_tflite/frozen_inference_graph.pb";
String prototxt = "./model_tflite/ssd_mobilenet_v1_coco.pbtxt";
Net net = readNetFromTensorflow(weights, prototxt);
//while (cap.read(frame))
while (1)
{
pthread_mutex_lock(&pmt);
pthread_cond_wait(&pct , &pmt);
Mat frame = imread("/home/proj/1.jpeg");
pthread_mutex_unlock(&pmt);
start = clock();
Size frame_size = frame.size();
Size cropSize;
if (frame_size.width / (float)frame_size.height > WHRatio)
{
cropSize = Size(static_cast<int>(frame_size.height * WHRatio),
frame_size.height);
}
else
{
cropSize = Size(frame_size.width,
static_cast<int>(frame_size.width / WHRatio));
}
Rect crop(Point((frame_size.width - cropSize.width) / 2,
(frame_size.height - cropSize.height) / 2),
cropSize);
Mat blob = blobFromImage(frame, 1. / 255, Size(300, 300));
//cout << "blob size: " << blob.size << endl;
net.setInput(blob);
Mat output = net.forward();
//cout << "output size: " << output.size << endl;
Mat detectionMat(output.size[2], output.size[3], CV_32F, output.ptr<float>());
frame = frame(crop);
float confidenceThreshold = 0.50;
for (int i = 0; i < detectionMat.rows; i++)
{
float confidence = detectionMat.at<float>(i, 2);
if (confidence > confidenceThreshold)
{
size_t objectClass = (size_t)(detectionMat.at<float>(i, 1));
int xLeftBottom = static_cast<int>(detectionMat.at<float>(i, 3) * frame.cols);
int yLeftBottom = static_cast<int>(detectionMat.at<float>(i, 4) * frame.rows);
int xRightTop = static_cast<int>(detectionMat.at<float>(i, 5) * frame.cols);
int yRightTop = static_cast<int>(detectionMat.at<float>(i, 6) * frame.rows);
ostringstream ss;
ss << confidence;
String conf(ss.str());
Rect object((int)xLeftBottom, (int)yLeftBottom,
(int)(xRightTop - xLeftBottom),
(int)(yRightTop - yLeftBottom));
rectangle(frame, object, Scalar(0, 255, 0), 2);
//cout << "objectClass:" << objectClass << endl;
String label = String(classNames[objectClass]) + ": " + conf;
//cout << "label"<<label << endl;
int baseLine = 0;
Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
rectangle(frame, Rect(Point(xLeftBottom, yLeftBottom - labelSize.height),
Size(labelSize.width, labelSize.height + baseLine)),
Scalar(0, 255, 0), -1);
putText(frame, label, Point(xLeftBottom, yLeftBottom),
FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0));
}
}
finish = clock();
totaltime = finish - start;
cout << "识别该帧图像所用的时间为:" << totaltime <<"ms"<< endl;
pthread_mutex_lock(&pmt_tflite);
imwrite("/home/proj/2.jpeg" , frame);
pthread_cond_broadcast(&pct_tflite);
pthread_mutex_unlock(&pmt_tflite);
waitKey(1);
}
//cap.release();
//waitKey(0);
return 0;
}
发送图片文件到HTTP WEB服务器的线程:
void * Thread_TCP_Web_Send_Only_JPEG_File(void *arg)
{
while(1)
{
if(flag_keep_alive && flag_post_once)
{
flag_post_once = 0;
HTTP_Send_Jpeg_File_Stream(fd_socket_conn , "/home/proj/1.jpeg" , &pmt , &pct);
}
}
}
void * Thread_TCP_Web_Send_Only_JPEG_File_Tflite(void *arg)
{
while(1)
{
if(flag_keep_alive_tflite && flag_post_once_tflite)
{
flag_post_once_tflite = 0;
HTTP_Send_Jpeg_File_Stream(fd_socket_conn_tflite , "/home/proj/2.jpeg" , &pmt_tflite , &pct_tflite);
}
}
}
-使用BMP280检测大气压和使用MPU6050运动检测都是用同一个方式即ioctl()的方式访问树莓派400的I2C1总线:
BMP280初始化:
int BMP280_Init()
{
uint8_t ret , i2c_read_data[2];
I2C_Device_Read(I2C_ADDR_BMP280 , &ret , 1 , 0xd0);
if (ret == 0x58)
{
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x88);
dig_t1 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x8a);
dig_t2 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x8c);
dig_t3 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x8e);
dig_p1 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x90);
dig_p2 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x92);
dig_p3 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x94);
dig_p4 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x96);
dig_p5 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x98);
dig_p6 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x9a);
dig_p7 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x9c);
dig_p8 = i2c_read_data[1] << 8 | i2c_read_data[0];
I2C_Device_Read(I2C_ADDR_BMP280 , i2c_read_data , 2 , 0x9e);
dig_p9 = i2c_read_data[1] << 8 | i2c_read_data[0];
return 0;
}
return -1;
}
BMP280读取气压值:
float BMP280_Read_Pressure()
{
I2C_Device_Read(I2C_ADDR_BMP280 , &msb , 1 , 0xf7);
I2C_Device_Read(I2C_ADDR_BMP280 , &lsb , 1 , 0xf8);
I2C_Device_Read(I2C_ADDR_BMP280 , &xlsb , 1 , 0xf9);
pres = (msb * 65536 | lsb * 256 | xlsb) >> 4;
I2C_Device_Read(I2C_ADDR_BMP280 , &msb , 1 , 0xfa);
I2C_Device_Read(I2C_ADDR_BMP280 , &lsb , 1 , 0xfb);
I2C_Device_Read(I2C_ADDR_BMP280 , &xlsb , 1 , 0xfc);
temp = (msb * 65536 | lsb * 256 | xlsb) >> 4;
var1 = (temp / 16384.0 - dig_t1 / 1024.0)*(dig_t2);
var2 = ((temp / 131072.0 - dig_t1 / 8192.0)*(temp / 131072.0 - dig_t1 / 8192.0))*dig_t3;
temp = var1 + var2;
temp /= 5120.0;
var1 = (temp / 2.0) - 64000.0;
var2 = var1 * var1*(dig_p6) / 32768.0;
var2 = var2 + var1 * (dig_p5)*2.0;
var2 = (var2 / 4.0) + ((dig_p4)*65536.0);
var1 = (dig_p3)*var1*var1 / 524288.0 + (dig_p2)*var1 / 524288.0;
var1 = (1.0 + var1 / 32768.0)*(dig_p1);
pres = 1048576.0 - pres;
pres = (pres - (var2 / 4096.0))*6250.0 / var1;
var1 = (dig_p9)*pres*pres / 2147483648.0;
var2 = pres * (dig_p8) / 32768.0;
pres = pres + (var1 + var2 + (dig_p7)) / 16.0;
return pres;
}
MPU6050初始化:
void MPU6050_Init()
{
uint8_t write_data[1];
write_data[0] = 0x07;
I2C_Device_Write (0x68 , write_data , 1 , SMPLRT_DIV);
//Write to sample rate register
write_data[0] = 0x01;
I2C_Device_Write (0x68 , write_data , 1 , PWR_MGMT_1);
// Write to power management register
write_data[0] = 0;
I2C_Device_Write (0x68 , write_data , 1 , CONFIG);
// Write to Configuration register
write_data[0] = 24;
I2C_Device_Write (0x68 , write_data , 1 , GYRO_CONFIG);
// Write to Gyro Configuration register
write_data[0] = 0x01;
I2C_Device_Write (0x68 , write_data , 1 , INT_ENABLE);
//Write to interrupt enable register
}
MPU6050读取GYRO值:
I2C_Device_Read(0x68 , mpu6050_read_data , 2 , ACCEL_XOUT_H);
accx = mpu6050_read_data[0] << 8 | mpu6050_read_data[1];
I2C_Device_Read(0x68 , mpu6050_read_data , 2 , ACCEL_YOUT_H);
accy = mpu6050_read_data[0] << 8 | mpu6050_read_data[1];
I2C_Device_Read(0x68 , mpu6050_read_data , 2 , ACCEL_ZOUT_H);
accz = mpu6050_read_data[0] << 8 | mpu6050_read_data[1];
I2C_Device_Read(0x68 , mpu6050_read_data , 2 , GYRO_XOUT_H);
gyrox = mpu6050_read_data[0] << 8 | mpu6050_read_data[1];
I2C_Device_Read(0x68 , mpu6050_read_data , 2 , GYRO_YOUT_H);
gyroy = mpu6050_read_data[0] << 8 | mpu6050_read_data[1];
I2C_Device_Read(0x68 , mpu6050_read_data , 2 , GYRO_ZOUT_H);
gyroz = mpu6050_read_data[0] << 8 | mpu6050_read_data[1];
-采集信号显示于图表,这边我用模拟生成的三个三角函数叠加波和实际从信号发生器生成的的波做显示:
图一为时域信号,图二为频域信号,时域信号不赘述,只贴频域信号生成的代码:
int FFTW_Mag_Test(float raw_data[] , float fft_data[] , int index_raw_data , int counts = 400)
{
signed short lX , lY;
float X , Y , Mag;
fftw_complex in[counts] , outf[counts] , outb[counts];
fftw_plan p;
int i , j , max_index = 0;
float max = 0;
float lBufOutArray_Real[N] = {0};
float lBufOutArray_Unreal[N] = {0};
float lBufMagArray[N] = {0};
//in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
//outf = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
//outb = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
for(i = 0 ; i < N ; i++)
{
in[0] = raw_data[i + index_raw_data];
in[1] = 0.0;
}
p = fftw_plan_dft_1d(N , in , outb , FFTW_BACKWARD , FFTW_ESTIMATE);
fftw_execute(p);
for(j = 0 ; j < N ; j++)
{
lBufOutArray_Real[j] = outb[j][0];
lBufOutArray_Unreal[j] = outb[j][1];
}
for(i = 0 ; i < N / 2 ; i ++)
{
X = lBufOutArray_Real;
Y = lBufOutArray_Unreal;
Mag = sqrt(X * X + Y * Y);
fft_data = Mag;
}
fftw_destroy_plan(p);
// if(in != NULL)
// fftw_free(in);
// if(outf != NULL)
// fftw_free(outf);
// if(outb != NULL)
// fftw_free(outb);
return 0;
}
还有峰值检测算法:
void Mag_Find_Peaks(float *src, float src_lenth, float distance, int *indMax, int *indMax_len, int *indMin, int *indMin_len)
{
int *sign = (int*)malloc(src_lenth * sizeof(int));
int max_index = 0,
min_index = 0;
*indMax_len = 0;
*indMin_len = 0;
for (int i = 1; i<src_lenth; i++)
{
float diff = src - src[i - 1];
if (diff>0) sign[i - 1] = 1;
else if (diff<0) sign[i - 1] = -1;
else sign[i - 1] = 0;
}
for (int j = 1; j<src_lenth - 1; j++)
{
float diff = sign[j] - sign[j - 1];
if (diff<0) indMax[max_index++] = j;
else if (diff>0)indMin[min_index++] = j;
}
int *flag_max_index = (int *)malloc(sizeof(int)*(max_index>min_index ? max_index : min_index));
int *idelete = (int *)malloc(sizeof(int)*(max_index>min_index ? max_index : min_index));
int *temp_max_index = (int *)malloc(sizeof(int)*(max_index>min_index ? max_index : min_index));
int bigger = 0;
float tempvalue = 0;
int i, j, k;
//波峰
for (int i = 0; i < max_index; i++)
{
flag_max_index = 0;
idelete = 0;
}
for (i = 0; i < max_index; i++)
{
tempvalue = -1;
for (j = 0; j < max_index; j++)
{
if (!flag_max_index[j])
{
if (src[indMax[j]] > tempvalue)
{
bigger = j;
tempvalue = src[indMax[j]];
}
}
}
flag_max_index[bigger] = 1;
if (!idelete[bigger])
{
for (k = 0; k < max_index; k++)
{
idelete[k] |= (indMax[k] - distance <= indMax[bigger] & indMax[bigger] <= indMax[k] + distance);
}
idelete[bigger] = 0;
}
}
for (i = 0, j = 0; i < max_index; i++)
{
if (!idelete)
temp_max_index[j++] = indMax;
}
for (i = 0; i < max_index; i++)
{
if (i < j)
indMax = temp_max_index;
else
indMax = 0;
}
max_index = j;
//波谷
for (int i = 0; i < min_index; i++)
{
flag_max_index = 0;
idelete = 0;
}
for (i = 0; i < min_index; i++)
{
tempvalue = 1;
for (j = 0; j < min_index; j++)
{
if (!flag_max_index[j])
{
if (src[indMin[j]] < tempvalue)
{
bigger = j;
tempvalue = src[indMin[j]];
}
}
}
flag_max_index[bigger] = 1;
if (!idelete[bigger])
{
for (k = 0; k < min_index; k++)
{
idelete[k] |= (indMin[k] - distance <= indMin[bigger] & indMin[bigger] <= indMin[k] + distance);
}
idelete[bigger] = 0;
}
}
for (i = 0, j = 0; i < min_index; i++)
{
if (!idelete)
temp_max_index[j++] = indMin;
}
for (i = 0; i < min_index; i++)
{
if (i < j)
indMin = temp_max_index;
else
indMin = 0;
}
min_index = j;
*indMax_len = max_index;
*indMin_len = min_index;
free(sign);
free(flag_max_index);
free(temp_max_index);
free(idelete);
}
峰值统计算法,分为直接printf打印到终端上和fprintf打印到文件上两种方式:
struct Peak_Value
{
int peak_count;
};
struct Peak_Stat
{
bool index_valid = false;
float peak_value_max_temp = 0;
struct Peak_Value pv[100] = {0};
};
Peak_Stat ps_100[100];
void Show_FFT_Analysis_Sum()
{
uint8_t i , j;
printf("\n=begin=\n");
for(i = 0 ; i < 100 ; i++)
{
//printf("peakFs[%d] = %d\n" , i , peakFs);
if(ps_100.index_valid == true)
{
printf("x = %d ; y_max_temp = %f ;\n" , i , ps_100.peak_value_max_temp);
for(j = 1 ; j < 100 ; j++)
{
if(ps_100.pv[j].peak_count > 0)
{
printf("x = %d ; y = %d ; peak_count = %d ;\n" , i , j ,
ps_100.pv[j].peak_count);
}
}
printf("\n");
}
}
printf("=end=\n\n");
}
void Write_CSV_File_FFT_Analysis_Sum(int dph , int fyear , int fmon , int fmday , int fhour ,
int fmin , int fsec , uint32_t masp , uint32_t mass)
{
uint8_t i , j;
FILE *fp;
char filename[200];
sprintf(filename , "//home//peaklog//peaklog_%02d_%02d:%02d:%02d_dph%02d.log" , fmday ,
fhour , fmin , fsec , dph);
fp = fopen(filename , "w+");
for(i = 0 ; i < 100 ; i++)
{
if(ps_100.index_valid == true)
{
for(j = 1 ; j < 100 ; j++)
{
if(ps_100.pv[j].peak_count > 0)
{
fprintf(fp , "%d , %d , %d ,\n" , i , j ,
ps_100.pv[j].peak_count);
}
}
fprintf(fp , "\n");
}
}
fclose(fp);
}
信号峰值统计结果是一个三维数组,即FFT结果(二维数组)在时间轴上的数组,X轴代表峰值位置(频率),Y轴代表X轴所在频率的峰值,Z轴代表Y轴在时间轴上的变化,使用信号峰值统计算法,可以抓出同一目标信号在相同时间的两次检测内的任何细微差别,但可忽略出现个数太少的Z值。bool index_valid表示该峰是否存在大于等于1的强度,有则为1,没有则为0,float peak_value_max_temp = 0;代表该峰的最高强度值。如图,将数据写入到log文件中:
每一行的第一个数代表信号峰编号,每一个信号峰对应一个频率,第二个数代表该信号峰的强度,第三个数代表该信号峰该强度的出现次数,比如4,18,1,代表4号峰的强度18出现了1次。
UDP接收信号电压值并处理:
void MainWindow::This_Thread_Line_Chart()
{
static int count = 0 , count_recv = 0;
int ret , i;
float pres;
while(1)
{
ret = recvfrom(socklen_udp_recv , (short * )&buffer_u16 , sizeof(buffer_u16) ,
0 , (struct sockaddr *)&sockaddr_udp_recv , &udp_addr_len);
if(ret > 0 && buffer_u16[0] == 0xadad && buffer_u16[1] == 0 && buffer_u16[2] == 0xffff)
{
if(flag_ffw_coll_start == true && flag_ffw_coll_stop == false)
{
-MP3播放器:
static QStringList fileNames_temp , fileNames_norepeat;
void MainWindow::on_PB_MP3_SELECT_clicked()
{
QFileDialog *fileDialog = new QFileDialog(this);
fileDialog->setWindowTitle(QStringLiteral("Select mp3 file(s)"));
fileDialog->setDirectory("./");
fileDialog->setNameFilter(tr("File(*.mp3*)"));
fileDialog->setFileMode(QFileDialog::ExistingFiles);
fileDialog->setViewMode(QFileDialog::Detail);
QStringList fileNames;
if (fileDialog->exec())
{
fileNames = fileDialog->selectedFiles();
ui->LE_MP3->setText(fileNames[0]);
}
fileNames_temp += fileNames;
for(int i = 0; i < fileNames_temp.length(); i++)
{
if(!fileNames_norepeat.contains(fileNames_temp))
{
fileNames_norepeat.append(fileNames_temp);
}
}
fileNames_temp.clear();
fileNames_temp = fileNames_norepeat;
ui->listWidget->clear();
ui->listWidget->addItems(fileNames_norepeat);
delete fileDialog;
}
void MainWindow::on_PB_MP3_PLAY_clicked()
{
system("killall -KILL madplay &");
sleep(1);
QString qs1 = "madplay " + ui->LE_MP3->text() + " &";
system(qs1.toLatin1().data());
on_PB_MP3_PAUSE_clicked_1();
}
void MainWindow::on_PB_MP3_PAUSE_clicked_1()
{
system("killall -CONT madplay &");
ui->PB_MP3_PAUSE->setText("PAUSE");
disconnect(ui->PB_MP3_PAUSE,SIGNAL(clicked()),this,SLOT(on_PB_MP3_PAUSE_clicked_1()));
connect(ui->PB_MP3_PAUSE,SIGNAL(clicked()),this,SLOT(on_PB_MP3_PAUSE_clicked()));
}
void MainWindow::on_PB_MP3_PAUSE_clicked()
{
system("killall -STOP madplay &");
ui->PB_MP3_PAUSE->setText("CONT");
disconnect(ui->PB_MP3_PAUSE,SIGNAL(clicked()),this,SLOT(on_PB_MP3_PAUSE_clicked()));
connect(ui->PB_MP3_PAUSE,SIGNAL(clicked()),this,SLOT(on_PB_MP3_PAUSE_clicked_1()));
}
void MainWindow::on_listWidget_clicked(const QModelIndex &index)
{
ui->LE_MP3->setText(ui->listWidget->currentItem()->text());
}
void MainWindow::on_listWidget_doubleClicked(const QModelIndex &index)
{
ui->LE_MP3->setText(ui->listWidget->currentItem()->text());
on_PB_MP3_PLAY_clicked();
}
void MainWindow::on_PB_MP3_PREV_clicked()
{
if(ui->listWidget->currentItem() != nullptr)
{
int index = ui->listWidget->currentRow();
if(index > 0)
index --;
if(index < 0)
index = ui->listWidget->count() - 1;
ui->listWidget->setCurrentRow(index);
ui->LE_MP3->setText(ui->listWidget->currentItem()->text());
on_PB_MP3_PLAY_clicked();
}
}
void MainWindow::on_PB_MP3_NEXT_clicked()
{
if(ui->listWidget->currentItem() != nullptr)
{
int index = ui->listWidget->currentRow();
index ++;
if(index >= ui->listWidget->count())
index = 0;
ui->listWidget->setCurrentRow(index);
ui->LE_MP3->setText(ui->listWidget->currentItem()->text());
on_PB_MP3_PLAY_clicked();
}
}
这个功能非常简单就不展开说了。
软件功能设计细节
-其实可以将main工程的所有功能整合到qtproj工程中,但是没必要,会造成qtproj工程代码及其臃肿,调试也不方便,main工程所实现的摄像头采集和人工智能物体识别分类功能其实并不需要额外的图形界面,因为目标图像是输出到HTTP WEB服务器上的,也就不需要整合到qtproj工程中。
-摄像头采集显示,人工智能物体识别分类需要两组锁,分别为
pthread_mutex_t pmt;
pthread_cond_t pct;
pthread_mutex_t pmt_tflite;
pthread_cond_t pct_tflite;
这是因为HTTP WEB服务器显示采集图像是需要等待摄像头采集结束,缓存数据稳定的,不然会造成画面撕裂或缺失,人工智能物体识别分类也需要一组锁,原因相同,人工智能物体识别分类需要输入图像和输出图像,其中输入图像是摄像头采集的图像,输出图像同样需要等待输入图像稳定,两组线程锁的锁广播位置可相同也可不同。
-qtproj工程中使用多线程功能,如果不使用友元类QProcess,则必须使用函数this指针传递方式:
void * MainWindow::Thread_Line_Chart(void *args)
{
MainWindow * pthis = (MainWindow *)args;
pthis->This_Thread_Line_Chart();
}
FILE *f;
void MainWindow::This_Thread_Line_Chart()
{
-QT Charts图表显示必须使用信号-槽机制,否则会造成进程crash,QT Charts图表数据更新步骤必须放置于槽函数中,信号可以是定时器溢出信号也可以是自定义信号。
-FFT的结果是信号个数一半,即输入信号是400个横坐标,则FFT的结果是200个横坐标,FFT结果是Y对称的,也就是可以定义400个横坐标的输出结果,但后200个左边的值是前200个坐标的值的轴对称镜像。
项目总结&视频演示
总演示视频:
15a329a3c4a134a3cb4b0d5f09de3009
树莓派400与树莓派4使用同款博通CPU,内存达到4GB,跑常规多媒体应用如MP3播放/MP4播放/UDP发送接收/HTTP WEB图片发送甚至是傅里叶变换算法都是没有任何问题的,完全没有卡顿的现象,只是GPU还不算顶流水准,跑Vector3D类对象还有点卡顿,然后就是没有内置NPU,单纯使用CPU跑tflite人工智能应用做物体归类还是卡顿的,总的来说,树莓派400性能中规中矩,商业化完全没问题,我这个项目也是致力于挖掘树莓派400的潜力,表现尚可。
【基于树莓派 Raspberry Pi 400的地铁安检控制一体化系统】物料开箱——树莓派400
https://bbs.eeworld.com.cn/thread-1210217-1-1.html
【基于树莓派400的图像识别归类&运动检测&模拟信号处理系统第一帖】MJPEG
https://bbs.eeworld.com.cn/thread-1222121-1-1.html
【基于树莓派400的图像识别归类&运动检测&模拟信号处理系统第二帖】使用I2C总线读取MPU6050/BMP280数据并搭建QT程序进行图形化显示(第一个视频)
https://bbs.eeworld.com.cn/thread-1222138-1-1.html
【基于树莓派400的图像识别归类&运动检测&模拟信号处理系统第三帖】使用QT程序内建的QTcharts库做信号处理/分析/统计(第二个视频)
https://bbs.eeworld.com.cn/thread-1222150-1-1.html
【基于树莓派400的图像识别归类&运动检测&模拟信号处理系统第四帖】尝试做个MP3播放器(第三个视频)
https://bbs.eeworld.com.cn/thread-1222158-1-1.html
【基于树莓派400的图像识别归类&运动检测&模拟信号处理系统第六帖-补充】树莓派400与STM32进行USB通信
https://bbs.eeworld.com.cn/thread-1223084-1-1.html
|