一、前言:
今天介绍一下我们的开源项目:音视频人脸、目标识别,音视频推流!
目前我们整个项目的人脸识别这块代码已经完成了:
RV1126人脸检测代码:
git clone https://gitee.com/harry12345123/rv1126_face_project.git
RV1126人脸识别代码:
git clone https://gitee.com/harry12345123/rv1126_face_recognize.git
板子移植教程暂时还没出来;现在马上把人脸识别这块的工程代码详细讲解完毕,就会出rv1126的板子的视频教程,这个板子是要大家自己去买的哈(我自己也是买的板子,给大家做教程!),这里选择rv1126,也是性价比比较高,有的朋友说rk3399或者rk3568可不可以,当然是可以,不过这里rk3399他没有npu支持的,也就是说,支持不了人脸识别和物体识别,这个要注意;这个可以在rk的官方介绍可以看到,rk3399pro是支持的:
rv1126:


rk3399:


后期的打算:
1、人脸识别、物体识别(目前已经完成人脸识别),马上开搞物体识别;
2、编解码框架梳理(硬解码的实现,这里其实都在mpp里面)和mpp 的讲解
3、rtmp/srt 推流实现
4、ffmpeg 的讲解(根据我们这个项目里面用到的知识来讲解,ffmpeg本身是比较大的!)
5、还有工作当中积累的技术经验分享!
ok ,我们今天继续人脸识别工程代码详细解读,上次已经把这两个接口详细介绍完了:

二、:read_camera_thread线程接口源码解读:
在讲解这块的源码之前,我先画执行流程图:

AVFifoBuffer *m_videoFifo = NULL;
int main(int argc, char *argv[])
{
int ret;
m_videoFifo = av_fifo_alloc(30 * av_image_get_buffer_size(AV_PIX_FMT_YUV420P, 640, 480, 1));
opencv_queue = new OPENCV_QUEUE();
init_asfort_device(APPID, SDKKEY);
init_face_data();
pthread_t pid;
ret = pthread_create(&pid, NULL, read_camera_thread, NULL);
if (ret != 0)
{
printf("Create Read_Camera_Thread Failed\n");
}
}
这里主要给 m_videoFifo缓冲队列去进行内存分配,AVFifoBuffer是ffmpeg里面的一个视频缓冲去,当然他还有一个音频缓冲去叫做AVAudioFifo;我们这里现在只专门处理视频,所以就不用这个音频缓冲区了。
恩,这就简单介绍到这里,下面我开始介绍read_camera_thread线程处理函数:
void *read_camera_thread(void *args)
{
pthread_detach(pthread_self());
avdevice_register_all();
//寻找自己的采集图像设备
//根据输入格式的名字查找AVInputFormat,在测试代码中,
//windows平台使用”vfwcap”(video for windows capture),linux平台使用”v4l2”(Video4Linux2);
AVInputFormat *in_fmt = av_find_input_format("video4linux2");
if (in_fmt == NULL)
{
printf("can't find_input_format\n");
// return;
}
// 设置摄像头的分辨率
AVDictionary *option = NULL;
char video_size[10];
sprintf(video_size, "%dx%d", WIDTH, HEIGHT);
av_dict_set(&option, "video_size", video_size, 0);
AVFormatContext *fmt_ctx = NULL;
//主要用来打开输入流并存储到格式化上下文AVFormatContext中。
if (avformat_open_input(&fmt_ctx, CAMERA_DEV, in_fmt, &option) < 0)
{
printf("can't open_input_file\n");
// return;
}
else
{
printf("Success Open Camera\n");
}
// printf device info
av_dump_format(fmt_ctx, 0, CAMERA_DEV, 0);
struct SwsContext *sws_ctx;
// 图像格式转换:CAMERA_FMT --> ENCODE_FMT
sws_ctx = sws_getContext(WIDTH, HEIGHT, CAMERA_FMT,
WIDTH, HEIGHT, ENCODE_FMT, 0, NULL, NULL, NULL);
uint8_t *yuy2buf[4];
int yuy2_linesize[4];
//此函数的功能是按照指定的宽、高、像素格式来分析图像内存
int yuy2_size = av_image_alloc(yuy2buf, yuy2_linesize, WIDTH, HEIGHT, CAMERA_FMT, 1);
uint8_t *yuv420pbuf[4];
int yuv420p_linesize[4];
int yuv420p_size = av_image_alloc(yuv420pbuf, yuv420p_linesize, WIDTH, HEIGHT, ENCODE_FMT, 1);
// 初始化packet,存放编码数据
AVPacket *camera_packet = av_packet_alloc();
// 初始化frame,存放原始数据
int y_size = WIDTH * HEIGHT;
//函数的作用是通过指定像素格式、图像宽、图像高来计算所需的内存大小
int frame_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, WIDTH, HEIGHT, 1);
for (;;)
{
// 摄像头获取图像数据
av_read_frame(fmt_ctx, camera_packet);
memcpy(yuy2buf[0], camera_packet->data, camera_packet->size);
// 图像格式转化
sws_scale(sws_ctx, (const uint8_t **)yuy2buf, yuy2_linesize,
0, HEIGHT, yuv420pbuf, yuv420p_linesize);
//该接口使用了如下调用,该接口主要作用是清理AVPacket中的所有空间数据,
//清理完毕后进行初始化操作,并且将 data 与 size 置为0,方便下次调用
av_packet_unref(camera_packet);
//以字节数的方式返回当前AVFifoBuffer中剩余的容量,
//也就是你可以写入的内容的大小
if (av_fifo_space(m_videoFifo) >= frame_size)
{
pthread_mutex_lock(&workmutex);
//写size字节的数据到AVFifoBuffer中
av_fifo_generic_write(m_videoFifo, yuv420pbuf[0], y_size, NULL);
av_fifo_generic_write(m_videoFifo, yuv420pbuf[1], y_size / 4, NULL);
av_fifo_generic_write(m_videoFifo, yuv420pbuf[2], y_size / 4, NULL);
pthread_mutex_unlock(&workmutex);
}
}
//进行内存释放回收
av_packet_free(&camera_packet);
avformat_close_input(&fmt_ctx);
sws_freeContext(sws_ctx);
av_freep(yuy2buf);
av_freep(yuv420pbuf);
}
代码说明:
pthread_detach(pthread_self()):将主线程和子线程分离开,子线程结束后,自动回收内存资源。
avdevice_register_all():在使用libavdevice之前,必须先运行avdevice_register_all()对设备进行注册,否则就会出错,后面操作都是无效的,比如打开摄像头等操作!
AVInputFormat *in_fmt = av_find_input_format("video4linux2"):这里的AVInputFormat是解复用结构体,暂时不用管里面的具体成员,这里调用av_find_input_format这个接口,主要是用传参“video4linux2”来判断摄像头的输入格式是v4l2,这里也就是打开摄像头。这里顺便提一下什么是复用和解复用,所谓的复用就是把解复用出的数据重新合并成一个格式,解复用就是相反的意思了。
av_dict_set():这个接口主要来设置摄像头的分辨率大小
avformat_open_input()和 AVFormatContext:主要用于打开获取到摄像头输入媒体流并获取媒体流的信息,并摄像头获取到的信息流保存到AVFormatContext;而AVFormatContext是存放音视频格式信息上下文结构体
av_dump_format():打印上面的媒体流信息。
av_image_alloc():这里主要辅助查看不同数据格式所需要的内存大小,比如我们这里的代码使用了这个两种格式:
#define CAMERA_FMT AV_PIX_FMT_YUYV422 、、
#define ENCODE_FMT AV_PIX_FMT_YUV420P
uint8_t *yuy2buf[4];
int yuy2_linesize[4];
//此函数的功能是按照指定的宽、高、像素格式来分析图像内存
int yuy2_size = av_image_alloc(yuy2buf, yuy2_linesize, WIDTH, HEIGHT, CAMERA_FMT, 1);
uint8_t *yuv420pbuf[4];
int yuv420p_linesize[4];
int yuv420p_size = av_image_alloc(yuv420pbuf, yuv420p_linesize, WIDTH, HEIGHT, ENCODE_FMT, 1);
这两种格式可以在ffmpeg源码找到:

关于这两种packed和planar格式,具体的差别可以去查资料学习一下。
for 循环里面的处理:
for (;;)
{
// 摄像头捕获每一帧数据并保存到camera_packet里面去
av_read_frame(fmt_ctx, camera_packet);
//把camera_packet里面的数据拷贝到yuy2buf里面去
memcpy(yuy2buf[0], camera_packet->data, camera_packet->size);
// 图像格式转化,把packed格式转换成planar
sws_scale(sws_ctx, (const uint8_t **)yuy2buf, yuy2_linesize,
0, HEIGHT, yuv420pbuf, yuv420p_linesize);
//该接口使用了如下调用,该接口主要作用是清理AVPacket中的所有空间数据,
//清理完毕后进行初始化操作,并且将 data 与 size 置为0,方便下次调用,不然这里面就保存不了下次读取的数据了
av_packet_unref(camera_packet);
//以字节数的方式返回当前AVFifoBuffer中剩余的容量,
//也就是你可以写入的内容的大小,这里的视频缓冲去大小必须足够大
if (av_fifo_space(m_videoFifo) >= frame_size)
{
pthread_mutex_lock(&workmutex);
//不断写size字节的数据到AVFifoBuffer中
av_fifo_generic_write(m_videoFifo, yuv420pbuf[0], y_size, NULL);
av_fifo_generic_write(m_videoFifo, yuv420pbuf[1], y_size / 4, NULL);
av_fifo_generic_write(m_videoFifo, yuv420pbuf[2], y_size / 4, NULL);
pthread_mutex_unlock(&workmutex);
}
}
最后通过av_fifo_generic_write()接口不断往视频缓冲区里面去写yuv数据,方便我们后面要讲的process_asfort_recognize_thread线程处理函数不断从这个视频缓冲去里面去读数据!
三、最后:
今天的源码详细解读就分享到这里了!
明天我们这个项目工程会涨价到300,现在的价格在120!
这里说一下,我们这个工程项目收费是已经很低了,包括的内容也是比较多的,还有解答服务;不满大家说,我自己以前也是从买视频学习成长起来的(这算是对自己的一种投资!),市面上的视频教程,动不动就大好几千,所以相比我们这个,真的非常便宜了!
最为重要的是,我们这个工程,都是实战性的东西,非常符合平时各大芯片平台开发内容!我自己本身也是做这块的,还有另外一个工程师他也是做音视频这块的!所以这个内容放心!你现在看我的文章源码解析,都是有视频教程的,有不懂的地方,可以在解答群里面随时问!





