音视频开发—FFmpeg 打开摄像头进行RTMP推流

实验平台:Ubuntu20.04

摄像头:普通USB摄像头,输出格式为YUV422

1.配置RTMP服务器推流平台

使用Nginx 配置1935端口即可,贴上教程地址

ubuntu20.04搭建Nginx+rtmp服务器)

2.配置FFmpeg开发环境

过程较为简单,这里不再赘述,可以看博主的往期博客,贴上教程地址:配置FFmpeg开发环境 (Vscode+CMake

3.推流具体实现流程

总体流程图

在这里插入图片描述

3.1 设备初始化

有些摄像头可能支持输出多种参数,因此一定要检查摄像头支持的格式

v4l2-ctl: 一个命令行工具,用于控制和调试V4L2设备。可以查询设备信息、设置参数、捕获视频帧等。

v4l2-ctl --list-formats-ext  # 列出设备支持的所有格式
v4l2-ctl --set-fmt-video=width=1920,height=1080,pixelformat=H264  # 设置视频格式
v4l2-ctl --stream-mmap --stream-count=100 --stream-to=output.raw  # 捕获视频流

查看本次实验的摄像头的相关参数

marxist@ubuntu:~/Desktop/audio_test/build$ v4l2-ctl --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
	Type: Video Capture

	[0]: 'MJPG' (Motion-JPEG, compressed)
		Size: Discrete 1920x1080
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 640x480
			Interval: Discrete 0.008s (120.101 fps)
			Interval: Discrete 0.011s (90.000 fps)
			Interval: Discrete 0.017s (60.500 fps)
			Interval: Discrete 0.033s (30.200 fps)
		Size: Discrete 1280x720
			Interval: Discrete 0.017s (60.000 fps)
			Interval: Discrete 0.033s (30.500 fps)
		Size: Discrete 1024x768
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 800x600
			Interval: Discrete 0.017s (60.000 fps)
		Size: Discrete 1280x1024
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 320x240
			Interval: Discrete 0.008s (120.101 fps)
	[1]: 'YUYV' (YUYV 4:2:2)
		Size: Discrete 1920x1080
			Interval: Discrete 0.167s (6.000 fps)
		Size: Discrete 640x480
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 1280x720
			Interval: Discrete 0.111s (9.000 fps)
		Size: Discrete 1024x768
			Interval: Discrete 0.167s (6.000 fps)
		Size: Discrete 800x600
			Interval: Discrete 0.050s (20.000 fps)
		Size: Discrete 1280x1024
			Interval: Discrete 0.167s (6.000 fps)
		Size: Discrete 320x240
			Interval: Discrete 0.033s (30.000 fps)

由上述可知,摄像头一共支持两种格式,一是MJPG格式,已经由硬件压缩好的一种格式,一种就是常见的YUV422格式,YUV同样支持多种分辨率格式。

设置输入格式上下文,Linux系统对应的是V4L2,查找视频流信息

AVInputFormat *input_format = av_find_input_format("v4l2");
    if ((ret = avformat_open_input(&input_ctx, "/dev/video0", input_format, &options)) < 0)
    {
        fprintf(stderr, "Could not open input\n");
        return ret;
    }
    // 查找流信息
    ret = avformat_find_stream_info(input_ctx, NULL);
    if (ret < 0)
    {
        std::cerr << "could not find stream info" << std::endl;
        return -1;
    }
    // 查找视频流
    for (size_t i = 0; i < input_ctx->nb_streams; i++)
    {
        if (input_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            video_stream_index = i;
            break;
        }
    }
    if (video_stream_index == -1)
    {
        std::cerr << "no video stream found" << std::endl;
        return -1;
    }

3.2 初始化编码器

本次推流实验使用的是H264编码器,CPU软编码,没有使用到硬件编码,用到的库是X264。

主要流程为 查找编码器——分配编码器上下文——设置编码器参数——打开编码器

    // 查找编码器
    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec)
    {
        fprintf(stderr, "Could not find AV_CODEC_ID_H264\n");
        return -1;
    }

    // 分配编码器上下文
    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        std::cerr << "Could not allocate video codec context" << std::endl;
        return -1;
    }

    // 设置编码器参数
    codec_ctx->codec_id = codec->id;
    codec_ctx->bit_rate = 400000;
    codec_ctx->width = 1280;
    codec_ctx->height = 720;
    codec_ctx->time_base = (AVRational){1, 9};
    codec_ctx->framerate = (AVRational){9, 1};
    codec_ctx->gop_size = 10;
    codec_ctx->max_b_frames = 0;             // 不需要B帧
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 传入的420P 格式,而cam 默认输出422 则一会 需要作转换

    // 打开编码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        return -1;
    }

这里将B帧参数设置为了0,因为加入B帧之后,虽然提高了压缩效率,但是也显著增加了编码的复杂性。编码B帧需要更多的计算资源,因为它不仅需要前向预测,还需要后向预测。对于资源受限的设备(如移动设备、嵌入式系统等),不使用B帧可以减少编码器的负担。

3.3 设置输出流

这里的输出流地址则特指的RTMP服务器地址,也就是说FFmpeg将编码好的数据传输到RTMP服务器。如果需要写入到文件,输出流地址也可以是文件路径。

相关代码操作

 // 创建输出流
    AVStream *out_stream = avformat_new_stream(output_ctx, codec);
    if (!out_stream)
    {
        fprintf(stderr, "Could not avformat_new_stream\n");
        return -1;
    }
    // 从输入流复制参数到输出流
    avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
    out_stream->time_base = codec_ctx->time_base;

    // 打开输出URL
    if (!(output_ctx->oformat->flags & AVFMT_NOFILE))
    {
        if (avio_open(&output_ctx->pb, output_url, AVIO_FLAG_WRITE) < 0)
        {
            std::cerr << "Could not open output URL" << std::endl;
            return -1;
        }
    }

    // 写输出文件头
    if (avformat_write_header(output_ctx, NULL) < 0)
    {
        fprintf(stderr, "Could not write header\n");
        return -1;
    }

3.4 读取摄像头数据

av_read_frame(input_ctx, &pkt)

代码作用是从输入设备读取数据帧,封装到packet中。

根据上文已经获取到了视频流索引,在此判断一下是不是视频流,因为有些摄像头支持语音输入,packet中存放的也可能是音频流

pkt.stream_index == video_stream_index

3.5 颜色空间转换

在上述过程中, 已经指定输出YUV422的数据了因此需要转换为YUV420数据

大体流程为:原始帧—转换上下文—YUV420帧

首先初始化转换上下文

 // 准备颜色空间色彩转换
    SwsContext *sws_ctx = sws_getContext(
        codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUYV422,
        codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
        SWS_BILINEAR, NULL, NULL, NULL);

    if (!sws_ctx)
    {
        std::cerr << "Could not initialize the conversion context" << std::endl;
        return -1;
    }

分辨率与编码器参数保持一致

准备原始数据帧,从摄像头读取的数据包中得到

AVFrame *temp_frame = av_frame_alloc();
                if (!temp_frame)
                {
                    std::cerr << "Could not allocate temporary frame" << std::endl;
                    av_packet_unref(&pkt);
                    continue;
                }

                // 分配临时帧的内存空间
                if (av_image_alloc(temp_frame->data, temp_frame->linesize, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUYV422, 1) < 0)
                {
                    std::cerr << "Could not allocate temporary frame buffer" << std::endl;
                    av_frame_free(&temp_frame);
                    av_packet_unref(&pkt);
                    continue;
                }

                // 将pkt.data中的数据填充到temp_frame
                ret = av_image_fill_arrays(temp_frame->data, temp_frame->linesize, pkt.data, AV_PIX_FMT_YUYV422, codec_ctx->width, codec_ctx->height, 1);
                if (ret < 0)
                {
                    std::cerr << "Error filling arrays" << std::endl;
                    av_freep(&temp_frame->data[0]);
                    av_frame_free(&temp_frame);
                    av_packet_unref(&pkt);
                    continue;
                }

初始化YUV420的帧

    // 分配AVFrame并设置参数
    AVFrame *frame = av_frame_alloc();
    if (!frame)
    {
        std::cerr << "Could not allocate video frame" << std::endl;
        return -1;
    }
    frame->format = codec_ctx->pix_fmt;
    frame->width = codec_ctx->width;
    frame->height = codec_ctx->height;
    av_frame_get_buffer(frame, 32);

最后执行转换即可

sws_scale(sws_ctx, temp_frame->data, temp_frame->linesize, 0, codec_ctx->height, frame->data, frame->linesize);

3.6 编码并输出到RTMP服务器

得到YUV420的数据,就可以进行最后的操作了

 // 编码视频数据
                ret = avcodec_send_frame(codec_ctx, frame);
                if (ret < 0)
                {
                    std::cerr << "Error sending frame to encoder" << std::endl;
                    break;
                }
                while (ret >= 0)
                {
                    ret = avcodec_receive_packet(codec_ctx, &pkt);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                    {
                        break;
                    }
                    else if (ret < 0)
                    {
                        std::cerr << "Error encoding frame" << std::endl;
                        break;
                    }

                    // 将编码后的视频数据推送到RTMP服务器
                    pkt.stream_index = out_stream->index;
                    av_packet_rescale_ts(&pkt, codec_ctx->time_base, out_stream->time_base);
                    pkt.pos = -1;

                    ret = av_interleaved_write_frame(output_ctx, &pkt);
                    if (ret < 0)
                    {
                        std::cerr << "Error writing frame" << std::endl;
                        break;
                    }
                    av_packet_unref(&pkt);
                }

4.完整代码

extern "C"
{
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include "libavutil/imgutils.h"
}
#include <iostream>
#include <cstdlib>
using namespace std;

int main(int argc, char *argv[])
{
    const char *output_url = "rtmp://192.168.1.79:1935/orin/live"; // 替换为你的RTMP推流地址
    AVFormatContext *input_ctx = NULL;
    AVPacket pkt;
    int ret;
    int video_stream_index = -1;
    AVDictionary *options = nullptr; // 摄像头相关参数
    int64_t pts = 0;                 // 初始化 PTS
    // 初始化libavformat和注册所有muxers, demuxers和协议
    avdevice_register_all();
    avformat_network_init();
    // 打开摄像头开始

    //   // 摄像头支持多种参数,因此使用option 指定参数 最大支持到9帧
    av_dict_set(&options, "video_size", "1280*720", 0);
    av_dict_set(&options, "framerate", "9", 0);
    av_dict_set(&options, "input_format", "yuyv422", 0);

    AVInputFormat *input_format = av_find_input_format("v4l2");
    if ((ret = avformat_open_input(&input_ctx, "/dev/video0", input_format, &options)) < 0)
    {
        fprintf(stderr, "Could not open input\n");
        return ret;
    }
    // 查找流信息
    ret = avformat_find_stream_info(input_ctx, NULL);
    if (ret < 0)
    {
        std::cerr << "could not find stream info" << std::endl;
        return -1;
    }
    // 查找视频流
    for (size_t i = 0; i < input_ctx->nb_streams; i++)
    {
        if (input_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            video_stream_index = i;
            break;
        }
    }
    if (video_stream_index == -1)
    {
        std::cerr << "no video stream found" << std::endl;
        return -1;
    }

    // 查找编码器
    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec)
    {
        fprintf(stderr, "Could not find AV_CODEC_ID_H264\n");
        return -1;
    }

    // 分配编码器上下文
    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx)
    {
        std::cerr << "Could not allocate video codec context" << std::endl;
        return -1;
    }

    // 设置编码器参数
    codec_ctx->codec_id = codec->id;
    codec_ctx->bit_rate = 400000;
    codec_ctx->width = 1280;
    codec_ctx->height = 720;
    codec_ctx->time_base = (AVRational){1, 9};
    codec_ctx->framerate = (AVRational){9, 1};
    codec_ctx->gop_size = 10;
    codec_ctx->max_b_frames = 0;             // 不需要B帧
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 传入的420P 格式,而cam 默认输出422 则一会 需要作转换

    // 打开编码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        return -1;
    }

    // 分配输出上下文
    AVFormatContext *output_ctx = nullptr;
    ret = avformat_alloc_output_context2(&output_ctx, NULL, "flv", output_url);
    if (!output_ctx)
    {
        fprintf(stderr, "Could not create output context\n");
        return ret;
    }

    // 创建输出流
    AVStream *out_stream = avformat_new_stream(output_ctx, codec);
    if (!out_stream)
    {
        fprintf(stderr, "Could not avformat_new_stream\n");
        return -1;
    }
    // 从输入流复制参数到输出流
    avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
    out_stream->time_base = codec_ctx->time_base;

    // 打开输出URL
    if (!(output_ctx->oformat->flags & AVFMT_NOFILE))
    {
        if (avio_open(&output_ctx->pb, output_url, AVIO_FLAG_WRITE) < 0)
        {
            std::cerr << "Could not open output URL" << std::endl;
            return -1;
        }
    }

    // 写输出文件头
    if (avformat_write_header(output_ctx, NULL) < 0)
    {
        fprintf(stderr, "Could not write header\n");
        return -1;
    }

    // 分配AVFrame并设置参数
    AVFrame *frame = av_frame_alloc();
    if (!frame)
    {
        std::cerr << "Could not allocate video frame" << std::endl;
        return -1;
    }
    frame->format = codec_ctx->pix_fmt;
    frame->width = codec_ctx->width;
    frame->height = codec_ctx->height;
    av_frame_get_buffer(frame, 32);

    // 准备颜色空间色彩转换
    SwsContext *sws_ctx = sws_getContext(
        codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUYV422,
        codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
        SWS_BILINEAR, NULL, NULL, NULL);

    if (!sws_ctx)
    {
        std::cerr << "Could not initialize the conversion context" << std::endl;
        return -1;
    }

    while (true)
    {
        if (av_read_frame(input_ctx, &pkt) >= 0)
        {
            if (pkt.stream_index == video_stream_index)
            {
                // 从相机出来的原始帧 为YUV 422 需要转换为420P
                // 数据是YUYV422格式,需要转换为YUV420P
                AVFrame *temp_frame = av_frame_alloc();
                if (!temp_frame)
                {
                    std::cerr << "Could not allocate temporary frame" << std::endl;
                    av_packet_unref(&pkt);
                    continue;
                }

                // 分配临时帧的内存空间
                if (av_image_alloc(temp_frame->data, temp_frame->linesize, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUYV422, 1) < 0)
                {
                    std::cerr << "Could not allocate temporary frame buffer" << std::endl;
                    av_frame_free(&temp_frame);
                    av_packet_unref(&pkt);
                    continue;
                }

                // 将pkt.data中的数据填充到temp_frame
                ret = av_image_fill_arrays(temp_frame->data, temp_frame->linesize, pkt.data, AV_PIX_FMT_YUYV422, codec_ctx->width, codec_ctx->height, 1);
                if (ret < 0)
                {
                    std::cerr << "Error filling arrays" << std::endl;
                    av_freep(&temp_frame->data[0]);
                    av_frame_free(&temp_frame);
                    av_packet_unref(&pkt);
                    continue;
                }

                // 转换颜色空间到YUV420P
                sws_scale(sws_ctx, temp_frame->data, temp_frame->linesize, 0, codec_ctx->height, frame->data, frame->linesize);
                // 设置帧的 PTS
                frame->pts = pts++;
                // 编码视频数据
                ret = avcodec_send_frame(codec_ctx, frame);
                if (ret < 0)
                {
                    std::cerr << "Error sending frame to encoder" << std::endl;
                    break;
                }
                while (ret >= 0)
                {
                    ret = avcodec_receive_packet(codec_ctx, &pkt);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                    {
                        break;
                    }
                    else if (ret < 0)
                    {
                        std::cerr << "Error encoding frame" << std::endl;
                        break;
                    }

                    // 将编码后的视频数据推送到RTMP服务器
                    pkt.stream_index = out_stream->index;
                    av_packet_rescale_ts(&pkt, codec_ctx->time_base, out_stream->time_base);
                    pkt.pos = -1;

                    ret = av_interleaved_write_frame(output_ctx, &pkt);
                    if (ret < 0)
                    {
                        std::cerr << "Error writing frame" << std::endl;
                        break;
                    }
                    av_packet_unref(&pkt);
                }

                av_frame_free(&temp_frame);
            }
        }
    }

    av_write_trailer(output_ctx);

    // 释放资源
    av_frame_free(&frame);
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&input_ctx);
    if (output_ctx && !(output_ctx->oformat->flags & AVFMT_NOFILE))
    {
        avio_closep(&output_ctx->pb);
    }
    avformat_free_context(output_ctx);
    sws_freeContext(sws_ctx);

    return 0;
}

5.获取推流数据

常用的工具为VLC播放器,选择打开网络串流地址

在这里插入图片描述

就能播放推流画面了

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【龙晰 离线安装openssl-devel】openssl-devel rpm 离线安装 需要下载哪些安rpm 包

进入龙晰镜像源地址下载 http://mirrors.openanolis.cn/anolis/8/BaseOS/x86_64/os/Packages/(base) [rootAI lib64]# yum install openssl-devel Last metadata expiration check: 14:03:32 ago on Fri 21 Jun 2024 07:26:56 AM CST. Dependencies resolved. Package …

不服 GPT-4o ,就是干?

OpenAI 最大的对手 Anthropic &#xff0c;深夜发布了他们迄今为止最智能的模型&#xff1a;Claude 3.5 Sonnet。 这俩家公司的竞争愈演愈烈&#xff0c;Anthropic 声称新 Claude AI 超越了 GPT-4o。 通过 Anthropic 发布的测试报告来看&#xff0c;新模型在推理、知识储备、代…

怎么处理整合了shiro的应用的RPC接口鉴权问题

这篇文章分享一下&#xff1a;当一个服务提供者整合了shiro安全框架来实现权限访问控制时&#xff0c;服务消费者通过feign请求服务提供者的接口时的鉴权不通过问题。 问题描述 博主有一个项目pms&#xff08;权限管理系统&#xff09;&#xff0c;使用了shiro框架来实现鉴权功…

什么是局域网IP?

局域网IP&#xff08;Local Area Network IP&#xff09;指的是在局域网内使用的IP地址。局域网是指在某个地理区域内&#xff0c;由一组相互连接的计算机组成的小型网络&#xff0c;常见于家庭、学校、办公室等场所。局域网IP可以用来实现内网穿透&#xff0c;即在复杂的网络环…

http发展史(http0.9、http1.0、http1.1、http/2、http/3)详解

文章目录 HTTP/0.9HTTP/1.0HTTP/1.1队头阻塞&#xff08;Head-of-Line Blocking&#xff09;1. TCP 层的队头阻塞2. HTTP/1.1 的队头阻塞 HTTP/2HTTP/3 HTTP/0.9 发布时间&#xff1a;1991年 特点&#xff1a; 只支持 GET 方法没有 HTTP 头部响应中只有 HTML 内容&#xff0…

2.XSS-存储型

储存型XSS 或持久型 XSS 交互的数据会被存在在数据库里面,永久性存储,具有很强的稳定性。 在留言板里面进行测试一下是否有做过滤 "<>?&66666点击提交 查看元素代码&#xff0c;已经提交完成&#xff0c;并且没有做任何的过滤措施 接下来写一个javascrip…

2024年十大数据集成工具和软件应用场景解析

详细阐述了十大数据集成工具及其优缺点&#xff1a; 数据集成的挑战 在当今的商业环境中&#xff0c;企业常常面临着数据分散、系统孤立的难题。传统的数据集成方式&#xff0c;就像一张复杂的蜘蛛网&#xff0c;难以理顺&#xff0c;令人头痛不已。 数据孤岛: 各个业务系统独立…

使用Jetpack Compose为Android App创建自定义页面指示器

使用Jetpack Compose为Android App创建自定义页面指示器 在现代移动应用中&#xff0c;页面指示器在提供视觉导航提示方面发挥着重要作用&#xff0c;帮助用户理解其在应用内容中的当前位置。页面指示器特别适用于顺序展示内容的场景&#xff0c;如图片轮播、图像库、幻灯片放…

在自托管基础设施上使用 GitOps 部署 MinIO

基于MinIO Weaviate Python GitOps探索的见解&#xff0c;本文探讨了如何增强软件部署流程的自动化。 通过将 GitHub Actions 与 Docker Swarm 集成而产生的协同作用&#xff0c;以自托管基础架构的稳健性为基础&#xff0c;标志着 CI/CD 实践的关键进步。这种方法不仅利用了软…

Mybatis缓存测试

实体类 Student Data Table(name "student") public class StudentEntity implements Serializable {private static final long serialVersionUID 1L;IdColumn(name "id")private Long id;Column(name "name")private String name;Column…

网络爬虫设置代理服务器

目录 1&#xff0e;获取代理 IP 2&#xff0e;设置代理 IP 3. 检测代理 IP 的有效性 4. 处理异常 如果希望在网络爬虫程序中使用代理服务器&#xff0c;就需要为网络爬虫程序设置代理服务器。 设置代理服务器一般分为获取代理 IP 、设置代理 IP 两步。接下来&#xff0c;分…

【STM32--Cortex-M3】

STM32-Cortex-M3 ■ Cortex-M3 处理器内核到基于Cortex-M3的MCU■ ARM的各种架构版本■ 指令集■ Cortex-M3简介■ Cortex-M3寄存器组■ Cortex-M3■ Cortex-M3■ Cortex-M3 ■ Cortex-M3 处理器内核到基于Cortex-M3的MCU Cortex-M3处理器内核是单片机的中央处理单元&#xff…

JDK18特性

JDK18特性 一、JAVA18概述 Java 18 在 2022 年 3 月 22 日正式发布,Java 18 不是一个长期支持版本,这次更新共带来 9 个新功能。 https://openjdk.org/projects/jdk/18/ 二、具体新特性 1. 默认UTF-8字符编码 JDK 一直都是支持 UTF-8 字符编码,这次是把 UTF-8 设置为了默…

RPM 打包入门(基于openEuler)

主要参考内容&#xff08;均为官方文档&#xff09;&#xff1a; https://rpm-packaging-guide.github.io/#building-rpms https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/8/html/packaging_and_distributing_software/introduction-to-rpm_packagin…

Opencv学习项目5——pyzbar,numpy

上一次我们使用pyzbar进行解码二维码并将其内容显示在图像上&#xff0c;使用的是rect barcode.rect来获取图像的坐标&#xff0c;这次我们使用另一种方法来获取坐标进行画框。 Numpy介绍 NumPy 是一个用于科学计算的开源 Python 库&#xff0c;提供了对大量数值数据进行高效操…

Java学习笔记(一)Java内容介绍、程序举例、DOS命令、Java跨平台特性的本质

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍Java内容介绍、程序举例、DOS命令、Java跨平台特性的本质详细介绍以及部分理论知识 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 🍉博主收将持续更新学习记录获,友友们有任何问题可以在评论区留言 目录 1、内容介绍…

STM32项目分享:家庭环境监测系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 2.PCB板打样焊接图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.…

数据可视化实验四:Pyecharts数据可视化

目录 一、使用PyEcharts绘制全国肺炎确诊人数分布图 1.1 柱状图 1.1.2 代码实现 1.1.2 绘制结果 1.2 饼状图 1.2.1 代码实现 1.2.2 绘制结果 1.3 使用over lap实现图形叠加 1.3.1 代码实现 1.3.2 绘制结果 1.4 地图绘制-Map 1.4.1 代码实现 1.4.2 绘制结果 1.5 地…

ECharts 词云图案例二:创意蒙版应用

ECharts 词云图案例二&#xff1a;创意蒙版应用 引言 在数据可视化领域&#xff0c;ECharts 以其强大的功能性和灵活性&#xff0c;成为开发者和设计师的首选工具之一。继上一篇关于 ECharts 词云图的详细介绍后&#xff0c;本文将探索词云图的进阶应用——使用蒙版来创造更具…

谷歌手机刷机教学

注意&#xff1a;手机已经解开了oem锁和bl 1、adb基础命令 连接设备adb devices&#xff1a;列出当前连接的所有设备。 adb connect <设备IP>&#xff1a;通过IP地址连接设备&#xff08;用于无线连接&#xff09;。 设备信息adb shell getprop&#xff1a;获取设备的所…