Qt/C++摄像头采集/二维码解析/同时采集多路/图片传输/分辨率帧率可调/自动重连

news2024/11/24 17:45:27

一、前言

本地摄像头的采集可以有多种方式,一般本地摄像头会通过USB的方式连接,在嵌入式上可能大部分是CMOS之类的软带的接口,这些都统称本地摄像头,和网络摄像头最大区别就是一个是通过网络来通信,一个是直接本地通信。本地摄像机的采集可以用qcamera来做,但是qcamera类在很多平台没有实现,比如嵌入式linux系统上几乎是没有用的,所以更加推荐用兼容性跨平台最好的ffmpeg来做,在linux上当然也可以通过v4l2来实现,这个其实是整个linux系统中通用的USB摄像头采集的框架,无论是ffmpeg还是qcamera,在linux系统上底层其他都是用v4l2来实现的,所以如果在嵌入式板子受限于内存或者存储空间大小,用不了ffmpeg一堆库,这个时候就可以用最底层的v4l2来采集。

很多人的应用场景要求不止采集1路本地摄像头,可能有多路都需要同时采集,其实这个和程序没有太大关系,能够采集一路肯定就能多路,毕竟就是封装的一个类,直接new出来就行,要能采集多路最大的性能瓶颈在USB带宽,数据带宽,还有就是USB口子的供电足不足,供电不足,也只能采集1-2路,比如有些人用的是USB集线器,上面一排USB口子,由于供电不足,就算你插4个USB摄像头,也只能最多采集2路,带宽又限制了分辨率和帧率,比如一个普通PC机器可以同时采集4路640x480分辨率,但是由于带宽不足,无法同时采集4路1080P,尽管4个摄像头都支持1080p分辨率,所以这是个综合因素的叠加影响,遇到问题慢慢查。

这个摄像头综合应用,陆陆续续完善了很多年,最开始是v4l2版本,只能用在嵌入式linux上,而后从Qt5开始集成了qcamera类,所以有用qcamera来做了一个版本,现在Qt6在多媒体框架有了巨大的性能提升,又用Qt6的qcamera做了个版本,为什么不通用了?因为Qt6的多媒体框架做了巨大更新调整,完全不兼容之前的类。在做视频组件的实现用ffmpeg采集本地摄像头也实现了,而且做的比较完善,所以后面单独提炼出来做了ffmpeg的版本,最后测试下来发现ffmpeg是最通用的,在哪里都能正常采集。

做二维码采集这个功能,用的是zxing类,最初的做法是将采集的图片发给zxing解析,实际过程发现大图片解析很慢,比如1080P图片大概1s只能解析3张,这个速度看起来就慢了点,如果是2K的分辨率呢。后面经过一些真实场景应用,包括菜鸟驿站里面带的扫描识别,发现都有个放大区域,用户可以设置一个关心的矩形区域,要放在这个矩形区域内,能够最大最快的识别,这是个不错的策略,在ffmpeg中通过设置crop裁剪区域就可以直接将对应内容放大显示,然后采集的实时画面可以是原视频,发送给zxing解析的就是裁剪后的画面,这样速度大大提升。之前大图就算二维码在里面,由于在整个图片中的占比区域太小,也可能解析失败,自动调整后裁剪区域,性能大幅度提升,完美解决。

二、效果图

在这里插入图片描述

三、体验地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 体验地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_camera。

四、相关代码

#include "frmzxing.h"
#include "frmmain.h"
#include "ui_frmzxing.h"
#include "qthelper.h"
#include "camerahelper.h"
#include "widgethelper.h"
#include "zxingthread.h"

frmZxing::frmZxing(QWidget *parent) : QWidget(parent), ui(new Ui::frmZxing)
{
    ui->setupUi(this);
    this->initForm();
    this->initConfig();
}

frmZxing::~frmZxing()
{
    delete ui;
}

void frmZxing::initForm()
{
    ui->frame->setFixedWidth(AppData::RightWidth);

    //关联信号槽
    connect(ui->cameraWidget, SIGNAL(sig_receivePlayStart(int)), this, SLOT(receivePlayStart(int)));
    connect(ui->cameraWidget, SIGNAL(sig_receivePoint(int, QPoint)), this, SLOT(receivePoint(int, QPoint)));

    //实例化解析类并启动线程
    zxing = new ZXingThread(this);
    connect(zxing, SIGNAL(receiveResult(QString, QString, int)), this, SLOT(receiveResult(QString, QString, int)));
    zxing->start();

    //启动定时器截图
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(on_btnSnap_clicked()));
    if (AppConfig::Zxing_Interval > 0) {
        timer->start(AppConfig::Zxing_Interval);
    }
}

void frmZxing::initConfig()
{
    CameraHelper::loadCameraCore(ui->cboxCameraCore, AppConfig::Zxing_CameraCore);
    connect(ui->cboxCameraCore, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));

    this->cameraDeviceChanged();
    connect(ui->cboxCameraName->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));

    CameraHelper::loadVideoSize(ui->cboxVideoSize);
    ui->cboxVideoSize->lineEdit()->setText(AppConfig::Zxing_VideoSize);
    connect(ui->cboxVideoSize->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));

    CameraHelper::loadFrameRate(ui->cboxFrameRate);
    ui->cboxFrameRate->lineEdit()->setText(QString::number(AppConfig::Zxing_FrameRate));
    connect(ui->cboxFrameRate->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));

    ui->cboxVideoMode->setCurrentIndex(AppConfig::Zxing_VideoMode);
    connect(ui->cboxVideoMode, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));

    zxing->setDecoder(AppConfig::Zxing_DecoderFormat);
    ui->cboxDecoderFormat->addItem("仅二维码");
    ui->cboxDecoderFormat->addItem("一维二维");
    ui->cboxDecoderFormat->addItem("所有格式");
    ui->cboxDecoderFormat->setCurrentIndex(AppConfig::Zxing_DecoderFormat);
    connect(ui->cboxDecoderFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));

    ui->cboxInterval->addItem("暂停");
    ui->cboxInterval->addItem("300");
    ui->cboxInterval->addItem("500");
    ui->cboxInterval->addItem("800");
    ui->cboxInterval->addItem("1000");
    int index = ui->cboxInterval->findText(QString::number(AppConfig::Zxing_Interval));
    ui->cboxInterval->setCurrentIndex(index < 0 ? 0 : index);
    connect(ui->cboxInterval, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));

    ui->txtTopLeft->setText(AppConfig::Zxing_TopLeft);
    connect(ui->txtTopLeft, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));

    ui->txtBottomRight->setText(AppConfig::Zxing_BottomRight);
    connect(ui->txtBottomRight, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));
}

void frmZxing::saveConfig()
{
    //内核变了需要重新搜索设备
    int cameraCore = ui->cboxCameraCore->itemData(ui->cboxCameraCore->currentIndex()).toInt();
    if (AppConfig::Zxing_CameraCore != cameraCore) {
        AppConfig::Zxing_CameraCore = cameraCore;
        this->cameraDeviceChanged();
    }

    //不为空才需要改变
    QString cameraName = ui->cboxCameraName->currentText();
    if (!cameraName.isEmpty()) {
        AppConfig::Zxing_CameraName = cameraName;
    }

    AppConfig::Zxing_VideoSize = ui->cboxVideoSize->currentText();
    AppConfig::Zxing_FrameRate = ui->cboxFrameRate->currentText().toInt();
    AppConfig::Zxing_VideoMode = ui->cboxVideoMode->currentIndex();

    //变了立即重新设置
    int decoderFormat = ui->cboxDecoderFormat->currentIndex();
    if (AppConfig::Zxing_DecoderFormat != decoderFormat) {
        AppConfig::Zxing_DecoderFormat = decoderFormat;
        zxing->setDecoder(AppConfig::Zxing_DecoderFormat);
    }

    int interval = ui->cboxInterval->currentText().toInt();
    if (AppConfig::Zxing_Interval != interval) {
        AppConfig::Zxing_Interval = interval;
        if (interval > 0) {
            timer->start(interval);
        } else {
            timer->stop();
        }
    }

    AppConfig::Zxing_TopLeft = ui->txtTopLeft->text().trimmed();
    AppConfig::Zxing_BottomRight = ui->txtBottomRight->text().trimmed();
    AppConfig::writeConfig();
}

void frmZxing::initPara()
{
    //设置显示窗体参数
    WidgetPara widgetPara = ui->cameraWidget->getWidgetPara();
    widgetPara.borderWidth = 1;
    widgetPara.videoMode = (VideoMode)AppConfig::Zxing_VideoMode;
    ui->cameraWidget->setWidgetPara(widgetPara);

    //设置采集线程参数
    CameraPara cameraPara = ui->cameraWidget->getCameraPara();
    cameraPara.cameraCore = (CameraCore)AppConfig::Zxing_CameraCore;
    cameraPara.cameraName = AppConfig::Zxing_CameraName;
    cameraPara.videoSize = AppConfig::Zxing_VideoSize;
    cameraPara.frameRate = AppConfig::Zxing_FrameRate;
    ui->cameraWidget->setCameraPara(cameraPara);
}

void frmZxing::append(int type, const QString &data, bool clear)
{
    static int maxCount = 50;
    static int currentCount = 0;
    QtHelper::appendMsg(ui->txtMain, type, data, maxCount, currentCount, clear);
    ui->txtMain->moveCursor(QTextCursor::End);
}

void frmZxing::createImage(const QString &text)
{
    if (!text.isEmpty()) {
        QImage image = zxing->encodeData(text, QSize(250, 250));
        ui->labResult->setImage(image, true);
    }
}

void frmZxing::cameraDeviceChanged()
{
    frmMain::getCameraInfo((CameraCore)AppConfig::Zxing_CameraCore);
    CameraHelper::loadCameraName(ui->cboxCameraName, AppConfig::Zxing_CameraName);
}

void frmZxing::snapImage(const QImage &image, const QString &snapName)
{
    QImage img = image;
    if (snapName != "file") {
        //有左上右下坐标说明需要裁减
        int w = image.width();
        int h = image.height();
        if (!AppConfig::Zxing_TopLeft.isEmpty() && !AppConfig::Zxing_BottomRight.isEmpty()) {
            QRect rect = WidgetHelper::getRect(AppConfig::Zxing_TopLeft, AppConfig::Zxing_BottomRight);
            img = img.copy(rect);
            QString msg = QString("执行裁剪(原分辨率: %1 x %2  新分辨率: %3 x %4)").arg(w).arg(h).arg(img.width()).arg(img.height());
            append(4, msg);
            img.save("f:/1.jpg", "jpg");
        }

        //防止图片过大导致解析很慢
        static int maxWidth = 1280;
        static int maxHeight = 720;
        w = img.width();
        h = img.height();
        if (w > maxWidth || h > maxHeight) {
            img = img.scaled(QSize(maxWidth, maxHeight), Qt::KeepAspectRatio);
            QString msg = QString("执行缩放(原分辨率: %1 x %2  新分辨率: %3 x %4)").arg(w).arg(h).arg(maxWidth).arg(maxHeight);
            append(4, msg);
        }
    }

    //添加到线程中解析
    zxing->append("", img);
    //显示图片
    ui->labImage->setImage(img, true);
}

void frmZxing::receiveResult(const QString &flag, const QString &text, int time)
{
    append(2, QString("(用时: %1 毫秒) 结果: %2").arg(time).arg(text));
    ui->txtResult->setText(text);
    //重新生成新的二维码图片
    createImage(text);
}

void frmZxing::receivePlayStart(int time)
{
    //如果存在裁剪区域则设置图形
    if (!AppConfig::Zxing_TopLeft.isEmpty() && !AppConfig::Zxing_BottomRight.isEmpty()) {
        GraphInfo graph;
        graph.rect = WidgetHelper::getRect(AppConfig::Zxing_TopLeft, AppConfig::Zxing_BottomRight);
        graph.borderWidth = WidgetHelper::getBorderWidth(ui->cameraWidget);
        ui->cameraWidget->setGraph(graph);
    }
}

void frmZxing::receivePoint(int type, const QPoint &point)
{
    QString text = QString("%1, %2").arg(point.x()).arg(point.y());
    if (ui->rbtnTopLeft->isChecked()) {
        ui->txtTopLeft->setText(text);
    } else {
        ui->txtBottomRight->setText(text);
    }
}

void frmZxing::on_btnPlay_clicked()
{
    this->initPara();
    if (!ui->cameraWidget->init()) {
        return;
    }

    //关联采集线程信号槽
    CameraThread *cameraThread = ui->cameraWidget->getCameraThread();
    connect(cameraThread, SIGNAL(cameraDeviceChanged()), this, SLOT(cameraDeviceChanged()));
    connect(cameraThread, SIGNAL(snapImage(QImage, QString)), this, SLOT(snapImage(QImage, QString)));

    ui->cameraWidget->play();
    ui->widget->setEnabled(false);
}

void frmZxing::on_btnStop_clicked()
{
    ui->cameraWidget->stop();
    ui->widget->setEnabled(true);
}

void frmZxing::on_btnPause_clicked()
{
    ui->cameraWidget->pause();
}

void frmZxing::on_btnNext_clicked()
{
    ui->cameraWidget->next();
}

void frmZxing::on_btnSnap_clicked()
{
    CameraThread *thread = ui->cameraWidget->getCameraThread();
    if (thread && thread->getIsOk()) {
        append(4, "执行截图并解析二维码");
        ui->cameraWidget->snap();
    }
}

void frmZxing::on_btnFile_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this, "打开", "", "*.jpg *.png");
    if (!fileName.isEmpty()) {
        QImage image(fileName);
        snapImage(image, "file");
    }
}

void frmZxing::on_btnCreate_clicked()
{
    createImage(ui->txtResult->text());
}

五、功能特点

  1. 同时支持 qcamera、ffmpeg、v4l2 三种内核解析本地摄像头。
  2. 提供函数 findCamera 自动搜索环境中的所有本地摄像头设备,搜索结果信号发出。
  3. 支持自动搜索和指定设备两种模式,自动搜索模式下会将搜索到的第一个设备作为当前设备打开。
  4. 支持同时打开多路设备,亲测4路,受限于具体的环境比如带宽。
  5. 支持自动重连,默认开启,失败后会自动重新搜索和尝试打开。
  6. ffmpeg方案、v4l2方案都支持回调模式(采集后转成QImage绘制)和句柄模式(采集后YUV数据GPU绘制,性能高)。
  7. 视频显示位置自动调整算法,当视频分辨率超过显示控件大小则等比例缩放居中显示,不超过则原尺寸居中显示,还可设置拉伸填充显示。(自动调整、等比例缩放、拉伸填充)。
  8. 可选不同的分辨率来打开摄像头,支持 160x120、320x240、640x480、800x600、1280x720、1280x960、1920x1080 等。
  9. 可选不同的帧率来打开摄像头,支持 0(采用默认值)、5、、10、15、20、25、30 等。
  10. 支持抓拍截图,传入文件名则自动保存截图文件,不传入则将图片数据QImage信号发出。
  11. 提供函数接口 开始播放play、停止播放stop、暂停播放pause、继续播放next。
  12. 支持动态热插拔加载,包括自动读取所有设备名称到下拉框。
  13. 支持录像文件存储,提供开始录像recordStart、暂停录像recordPause、停止录像recordStop 等函数。
  14. 提供二维码示例,自动采集画面识别二维码,支持自动将识别到的二维码重新生成大图。
  15. 二维码识别支持设置热点区域,对该区域内的图片进行裁切并识别,在大分辨率图像采集的时候非常有用,提升速度和效率。
  16. 支持选择图片文件解析二维码,手动输入文本内容生成二维码。
  17. 提供图片传输示例,自动将打开的摄像头视频实时传输出去,服务器端接收后解析显示。此方案可以作为将本地的摄像头实时画面远程传输,比如嵌入式板子上的摄像头画面传输到PC端显示。
  18. 支持等比例拉伸填充显示,画面宽高小于显示控件的宽高则以原视频大小为准,大于则按照显示控件的尺寸等比例缩放居中。
  19. 视频控件悬浮条自带开始和停止录像切换、声音静音切换、抓拍截图、关闭视频等功能。
  20. 音频组件支持声音波形值数据解析,可以根据该值绘制波形曲线和柱状声音条,默认提供了声音振幅信号。
  21. 代码框架和结构优化到极致,性能彪悍,持续迭代更新升级。
  22. 源码支持Qt4、Qt5、Qt6,兼容所有版本。

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

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

相关文章

小H靶场笔记:DC-5

DC-5 January 5, 2024 10:44 AM Tags&#xff1a;screen提权 Owner&#xff1a;只惠摸鱼 信息收集 探测靶机ip&#xff0c;大概率为192.168.199.135&#xff0c;且开放端口为80和111 扫描端口80&#xff0c;111相关服务、版本、操作系统、漏洞信息 sudo nmap -sS -O -sV 19…

如何让CHAT使用python绘制概率密度图像?

问CHAT&#xff1a;用python绘制概率密度图像 CHAT回复&#xff1a;你可以使用Python的matplotlib库和numpy库进行概率密度的绘制。 以下是一个简单的例子&#xff1a; python import numpy as np import matplotlib.pyplot as plt #随机生成1000个正态分布的数 data np.rand…

高性价比LDR6028Type-C转3.5mm音频和PD快充转接器

随着市面上的大部分手机逐渐取消了3.5mm音频耳机接口&#xff0c;仅保留一个Type-C接口&#xff0c;追求音质和零延迟的用户面临着一大痛点。对于这些用户&#xff0c;Type-C转3.5mm接口线的出现无疑是一大福音。这款线材在刚推出时就受到了手机配件市场的热烈欢迎&#xff0c;…

Linux 修改主机名称并通过主机名称访问服务器

一、命令提示符简介 当我们打开终端的时候&#xff0c;我们要输入命令的左边就是命令提示符&#xff0c;如下图&#xff0c;接下来介绍下他们分别代表什么含义 1、root 和 xhf 表示的是当前登录的用户名称。 2、node2 表示的当前的主机名称。 3、~ 表示的是当前的目录 4、# 表示…

贪心半平面求交 - 洛谷 - P2600 [ZJOI2008] 瞭望塔

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 往期相关背景半平面求交 点击前往 凸包点击前往 题目大意 题目链接 https://www.luogu.com.cn/problem/P2600 有一座山&#xff0c;可以用一条山的上方轮廓折线(x1,…

数据采集有哪些方法?HTTP代理起到什么作用?

在这个数字化的时代&#xff0c;数据就如同生活中不可或缺的元素&#xff0c;我们的行为、喜好、甚至是想法都被转化成了数字化的信息。那么&#xff0c;现代社会是如何进行数据的采集的呢&#xff1f;让我们一同来看看&#xff01; 1. 网络浏览行为的追踪 在我们浏览互联网的…

nacos 2.* 部署在linux服务器无法注册问题

通过sdk注册代码 报错 Exception in thread "main" ErrCode:-401, ErrMsg:Client not connected, current status:STARTING at com.alibaba.nacos.common.remote.client.RpcClient.request(RpcClient.java:639) at com.alibaba.nacos.common.remote.client…

Python基础篇: 环境安装

Python基础环境使用 一&#xff1a;运行环境Anaconda介绍1、Anaconda搭建1.1、下载方式1.2、安装1.3、验证是否安装成功 2、管理python环境2.1、列出所有环境2.2、创建环境2.3、进入指定虚拟环境2.4、离开虚拟环境2.5、删除虚拟环境 3、依赖管理3.1、安装依赖3.2、卸载依赖3.3、…

关于电气火灾监控产品在石化行业的应用探讨-安科瑞 蒋静

摘 要&#xff1a;通过对石油化工企业电气火灾成因的分析,针对目前剩余电流检测与故障电弧检测手段在石油化工企业应用中存在的问题&#xff0c;提出一种智慧防火解决方案。通过对各类电气火灾隐患进行集中监测&#xff0c;提高了电气火灾隐患检测的精度&#xff0c;从而达到避…

Springboot+vue的工作流程管理系统(有报告),Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的工作流程管理系统(有报告)&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的工作流程管理系统&#xff0c;采用M&#xff08;model&#xff09;V&am…

HarmonyOS 应用开发学习笔记 状态管理概述

移动端开发&#xff0c;最重要的一点就是数据的处理&#xff0c;并且正确的显示渲染UI。 变量在页面和组件、组件和组件之间有时候并不能实时共享&#xff0c;而有时候&#xff0c;又不需要太多的作用域&#xff08;节省资源&#xff09;&#xff0c;作用就需要根据不同场景&am…

bootstrap5实现宠物商店网站 Cat-Master

一、需求分析 宠物商店网站是指专门为宠物商店或宠物用品商家而建立的在线平台。这种网站的功能通常旨在提供以下服务&#xff1a; 产品展示&#xff1a;宠物商店网站通常会展示宠物食品、玩具、床上用品、健康护理产品等各种宠物用品的图片和详细信息。这样&#xff0c;潜在的…

欧科云链研究院:奔赴2024,Web3与AI共振引爆数字时代潘多拉魔盒

出品&#xff5c;欧科云链研究院 2024年&#xff0c;Web3与AI两个数字科技的巅峰碰撞&#xff0c;欧科云链研究院探索AI与Web3的技术融合&#xff0c;与澎湃科技联合发布2024年展望&#xff0c;原标题为《2024年展望&#xff1a;Web3与AI共振引爆可信数字社会》&#xff0c;共…

Proxmox VE 超融合集群销毁Ceph Pool

作者&#xff1a;田逸&#xff08;formyz&#xff09; 销毁Ceph Pool的目的 一套五节点的Proxmox VE超融合集群&#xff0c;当初为有效利用资源&#xff0c;配备了Nvme高性能磁盘和大容量的SATA机械磁盘&#xff08;如图所示&#xff09;&#xff0c;高性能Nvme磁盘用于虚拟机…

阿里云域名优惠口令2024年更新,注册、续费和转入可用

2024年阿里云域名优惠口令&#xff0c;com域名续费优惠口令“com批量注册更享优惠”&#xff0c;cn域名续费优惠口令“cn注册多个价格更优”&#xff0c;cn域名注册优惠口令“互联网上的中国标识”&#xff0c;阿里云优惠口令是域名专属的优惠码&#xff0c;可用于域名注册、续…

docker +gitee+ jenkins +maven项目 (二)

文章目录 前言一、创建Maven项目二、常规配置1.gitee配置2.gitee仓库配置3.构建时操作4.构建后操作 总结 前言 上一篇文章介绍了Jenkins的环境配置和工具配置&#xff0c;这篇进行具体maven项目的配置 一、创建Maven项目 二、常规配置 1.gitee配置 在工具哪里配置好gitee后&…

element-ui table height 属性导致界面卡死

问题: 项目上&#xff0c;有个点击按钮弹出抽屉的交互, 此时界面卡死 原因分析: 一些场景下(父组件使用动态单位/弹窗、抽屉中使用), element-ui 的 table 会循环计算高度值, 导致界面卡死 github 上的一些 issues 和解决方案: Issues ElemeFE/element GitHub 官方讲是升…

pyfolio工具结合backtrader分析量化策略组合,附源码+问题分析

pyfolio可以分析backtrader的策略&#xff0c;并生成一系列好看的图表&#xff0c;但是由于pyfolio直接install的稳定版有缺陷&#xff0c;开发版也存在诸多问题&#xff0c;使用的依赖版本都偏低&#xff0c;试用了一下之后还是更推荐quantstats。 1、安装依赖 pip install …

vue3中使用echarts:tooltip的trigger为axis tooltip不显示问题

vue3中使用echarts时&#xff0c;tooltip的trigger设置为axis时formatter不触发 tooltip: {trigger: "axis",formatter: function (params) {console.log("params", params);},axisPointer: {type: "shadow", // 阴影指示器}, },解决办法&#…

【Turtle库】海绵宝宝

在这个充满创意与想象力的时代&#xff0c;我们决定挑战一项富有意义且有趣的使命——使用Python编程语言绘制海绵宝宝。这个经典的角色&#xff0c;以其独特的魅力和无与伦比的搞笑天赋&#xff0c;已经成为了无数人心中的童年回忆。现在&#xff0c;我们希望通过Python的强大…