QTransform的使用

news2025/1/19 17:12:54

目录

  • 引言
  • 基础知识
    • 缩放矩阵
    • 平移矩阵
    • 旋转矩阵
    • 矩阵乘法
  • 实际使用
    • 实现思路
    • 完整代码
  • 参考资料

引言

A transformation specifies how to translate, scale, shear, rotate or project the coordinate system, and is typically used when rendering graphics.

A QTransform object can be built using the setMatrix(), scale(), rotate(), translate() and shear() functions. Alternatively, it can be built by applying basic matrix operations. The matrix can also be defined when constructed, and it can be reset to the identity matrix (the default) using the reset() function.

如上所述,QTransform的作用是平移、缩放、裁切、旋转或投影坐标系,通常在渲染图形时使用。QTransform本质是对矩阵的封装,将我们熟知的旋转矩阵、平移矩阵等,封装为清晰明了的函数,如rotate()、translate()等,方便程序调用。

既然是对矩阵的封装,自然就是应用在坐标的转换,从坐标系A转换到坐标系B,不过QTransform只支持2D场景下使用。实际使用场景多种多样,可以用于实现图元的放大缩小、图形的不规则展示、重复图形但有规律的绘制等等,能很好的满足QPainter、QOpenGLWidget中绘图的坐标系转换的使用。

基础知识

在实际使用之前,我们需要先了解矩阵的相关性质,这样才能够方便后续在程序中的使用。如果不彻底理解底层的实现,在实际调用的过程中很容易就被各种复杂的转换绕晕。虽然QTransform只支持2D,但为了更好的理解矩阵,以下例子中都是基于3D坐标举例,2D场景下将z值视为0即可。

缩放矩阵

[ S 1 0 0 0 0 S 2 0 0 0 0 S 3 0 0 0 0 1 ] × [ x y z 1 ] = [ S 1 × x S 2 × y S 3 × z 1 ] \left[\begin{array}{cccc} S_1&0&0&0\\ 0&S_2&0&0\\ 0&0&S_3&0\\ 0&0&0&1 \end{array}\right] \times \left[\begin{array}{c}x\\y\\z\\1 \end{array}\right] = \left[\begin{array}{c} S_1\times x\\ S_2\times y\\ S_3\times z\\ 1 \end{array}\right] S10000S20000S300001 × xyz1 = S1×xS2×yS3×z1
如上所示,原坐标点P1(x,y,z),通过旋转矩阵S转换为新坐标P2,其中S1、S2、S3代表不同坐标轴的缩放倍率。放在QTransform::scale中,sx和sy就分别对应S1和S2。

QTransform &QTransform::scale(qreal sx, qreal sy)

平移矩阵

[ 1 0 0 T x 0 1 0 T y 0 0 1 T z 0 0 0 1 ] × [ x y z 1 ] = [ T x + x T y + y T z + z 1 ] \left[\begin{array}{cccc} 1&0&0&T_x\\ 0&1&0&T_y\\ 0&0&1&T_z\\ 0&0&0&1 \end{array}\right] \times \left[\begin{array}{c}x\\y\\z\\1 \end{array}\right] = \left[\begin{array}{c} T_x + x\\ T_y + y\\ T_z + z\\ 1 \end{array}\right] 100001000010TxTyTz1 × xyz1 = Tx+xTy+yTz+z1
如上所示,原坐标点P1(x,y,z),通过旋转矩阵T转换为新坐标P2,其中Tx、Ty、Tz代表不同坐标轴的平移距离。放在QTransform::translate中,dx和dy就分别对应Tx和Ty。

QTransform &QTransform::translate(qreal dx, qreal dy)

旋转矩阵

绕X轴旋转:
[ 1 0 0 0 0 cos ⁡ θ − sin ⁡ θ 0 0 sin ⁡ θ cos ⁡ θ 0 0 0 0 1 ] × [ x y z 1 ] = [ x cos ⁡ θ × y − sin ⁡ θ × z sin ⁡ θ × y + cos ⁡ θ × z 1 ] \left[\begin{array}{cccc} 1&0&0&0\\ 0&\cos\theta&-\sin\theta&0\\ 0&\sin\theta&\cos\theta&0\\ 0&0&0&1 \end{array}\right] \times \left[\begin{array}{c}x\\y\\z\\1 \end{array}\right] = \left[\begin{array}{c} x\\ \cos\theta \times y -\sin\theta \times z\\ \sin\theta \times y+ \cos\theta \times z\\ 1 \end{array}\right] 10000cosθsinθ00sinθcosθ00001 × xyz1 = xcosθ×ysinθ×zsinθ×y+cosθ×z1
绕Y轴旋转:
[ cos ⁡ θ 0 sin ⁡ θ 0 0 1 0 0 − sin ⁡ θ 0 cos ⁡ θ 0 0 0 0 1 ] × [ x y z 1 ] = [ cos ⁡ θ × x + sin ⁡ θ × z y − sin ⁡ θ × x + cos ⁡ θ × z 1 ] \left[\begin{array}{cccc} \cos\theta&0&\sin\theta&0\\ 0&1&0&0\\ -\sin\theta&0&\cos\theta&0\\ 0&0&0&1 \end{array}\right] \times \left[\begin{array}{c}x\\y\\z\\1 \end{array}\right] = \left[\begin{array}{c} \cos\theta \times x + \sin\theta \times z\\ y\\ -\sin\theta \times x + \cos\theta \times z\\ 1 \end{array}\right] cosθ0sinθ00100sinθ0cosθ00001 × xyz1 = cosθ×x+sinθ×zysinθ×x+cosθ×z1
绕Z轴旋转:
[ cos ⁡ θ − sin ⁡ θ 0 0 sin ⁡ θ cos ⁡ θ 0 0 0 0 1 0 0 0 0 1 ] × [ x y z 1 ] = [ cos ⁡ θ × x − sin ⁡ θ × y sin ⁡ θ × x + cos ⁡ θ × y z 1 ] \left[\begin{array}{cccc} \cos\theta&-\sin\theta&0&0\\ \sin\theta&\cos\theta&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{array}\right] \times \left[\begin{array}{c}x\\y\\z\\1 \end{array}\right] = \left[\begin{array}{c} \cos\theta \times x -\sin\theta \times y\\ \sin\theta \times x + \cos\theta \times y\\ z\\ 1 \end{array}\right] cosθsinθ00sinθcosθ0000100001 × xyz1 = cosθ×xsinθ×ysinθ×x+cosθ×yz1
由于QTransfrom只支持2D,因此这里我们只需要了解绕Z轴旋转即可。原坐标点P1(x,y,z),通过旋转矩阵R转换为新坐标P2。放在QTransform::translate中,angle就是矩阵中的θ。这里可以看到还有一个参数axis,使用其他轴进行旋转时得到的新坐标是其在XY平面的投影。

QTransform &QTransform::rotate(qreal angle, Qt::Axis axis = Qt::ZAxis)

矩阵乘法

同时大量使用到矩阵的叉乘,运算规则可以简单理解为一行乘以一列,详细描述如下:

新矩阵的第i行第j列的元素的值,为第一个矩阵的第i行的每个元素分别乘上第二个矩阵第j列的每个元素然后进项相加

矩阵乘法一般不满足交换律(除了有些特殊的方阵之间的乘法),缩放矩阵属于该特殊矩阵,因此满足交换律,相乘时没有顺序要求,但是平移矩阵和旋转矩阵并不不满足交换律的,也就是
[ T ] × [ R ] × [ x y z 1 ] ≠ [ R ] × [ T ] × [ x y z 1 ] \left[\begin{array}{c} T \end{array}\right] \times \left[\begin{array}{c} R \end{array}\right] \times \left[\begin{array}{c}x\\y\\z\\1 \end{array}\right] \neq \left[\begin{array}{c} R \end{array}\right] \times \left[\begin{array}{c} T \end{array}\right] \times \left[\begin{array}{c}x\\y\\z\\1 \end{array}\right] [T]×[R]× xyz1 =[R]×[T]× xyz1
同时乘法又是右结合律的,如果你希望先旋转再平移,那么你需要将旋转放在后面,代码如下:

    QTransform transform;
    transform.translate(translate_x_, translate_y_);
    transform.rotate(rotate_);

效果如下(黄色虚线为原始图形,红色实线为先旋转45°再平移):
在这里插入图片描述

而如果需要先平移再旋转,则需要将平移放在后面,代码如下:

    QTransform transform;
    transform.translate(translate_x_, translate_y_);
    transform.rotate(rotate_);

效果如下(黄色虚线为原始图形,红色实线为先再平移再旋转45°,):
在这里插入图片描述

实际使用

有了矩阵的相关基础知识,可以开始实际代码的编写。具体功能如下:

  1. 默认矩形QRect(0, 0, 100, 100)
  2. 支持平移
  3. 支持绕矩形中心旋转

Demo实现效果如下:
在这里插入图片描述

实现思路

	QTransform transform;
    transform.translate(translate_x_, translate_y_);
    transform.rotate(rotate_);
    transform.translate(-rect_.width() / 2, -rect_.height() / 2);
    painter.setPen(QColor(244,67,54));
    QPolygon polygon = transform.mapToPolygon(rect_);
    painter.drawPolygon(polygon);

核心代码只有上述部分,通过QTransform 将矩形rect_转换为新的多边形polygon ,再将polygon 绘制出来。

按照之前说的右结合律的顺序,需要先将矩形rect_移动至其中心,再进行旋转界面设置的数值rotate_,再将其移动界面界面设置的数值translate_x_、translate_y_。

完整代码

自绘控件

class TestWidget : public QWidget
{
    Q_OBJECT

public:
    TestWidget(QWidget *parent = nullptr);

    void setRotate(int rotate);
    void setTranslateX(qreal translate_x);
    void setTranslateY(qreal translate_y);

protected:
    void paintEvent(QPaintEvent* event) override;

private:
    int rotate_;
    qreal translate_x_;
    qreal translate_y_;

    QRect rect_;
};
TestWidget::TestWidget(QWidget *parent)
    : QWidget(parent)
    , rotate_(0)
    , translate_x_(0)
    , translate_y_(0)
    , rect_(0, 0, 100, 100)
{

}

void TestWidget::setRotate(int rotate)
{
    rotate_ = rotate;
    update();
}

void TestWidget::setTranslateX(qreal translate_x)
{
    translate_x_ = translate_x;
    update();
}

void TestWidget::setTranslateY(qreal translate_y)
{
    translate_y_ = translate_y;
    update();
}


void TestWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    painter.save();
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::white);
    painter.drawRect(event->rect());
    painter.restore();

    // 坐标轴
    painter.save();
    painter.setPen(QColor(32, 32, 32, 255 * 0.5));
    QPoint p1(0, height() / 2);
    QPoint p2(width(), height() / 2);
    QPoint p3(width() / 2, 0);
    QPoint p4(width() / 2, height());
    painter.drawLine(p1, p2);
    painter.drawLine(p3, p4);
    // 箭头
    int interval = 6;
    QPainterPath arrows;
    arrows.lineTo(0, interval / 2);
    arrows.lineTo(interval, 0);
    arrows.lineTo(0, -interval / 2);
    {
        painter.save();
        painter.translate(width() - interval, height() / 2);
        painter.fillPath(arrows, QColor(128, 128, 128));
        painter.restore();
    }
    {
        painter.save();
        painter.translate(width() / 2, height() - interval);
        painter.rotate(90);
        painter.fillPath(arrows, QColor(128, 128, 128));
        painter.restore();
    }
    painter.restore();

    painter.translate(width() / 2, height() / 2);

    // 原矩形
    QPen pen(QColor(255,193,7));
    pen.setStyle(Qt::DashLine);
    painter.setPen(pen);
    painter.drawRect(rect_);

    // 新矩形
    QTransform transform;
    transform.translate(translate_x_, translate_y_);
    transform.rotate(rotate_);
    transform.translate(-rect_.width() / 2, -rect_.height() / 2);
    painter.setPen(QColor(244,67,54));
    QPolygon polygon = transform.mapToPolygon(rect_);
    painter.drawPolygon(polygon);
}

主窗体

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_spinBox_valueChanged(int arg1);
    void on_spinBox_2_valueChanged(int arg1);
    void on_spinBox_3_valueChanged(int arg1);

private:
    Ui::MainWindow *ui;
};
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPainter>
#include <QPaintEvent>
#include <QPainterPath>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //auto tmpWidget = new TestWidget(this);
    //setCentralWidget(tmpWidget);
}

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

void MainWindow::on_spinBox_valueChanged(int arg1)
{
    ui->widget->setRotate(arg1);
}

void MainWindow::on_spinBox_2_valueChanged(int arg1)
{
    ui->widget->setTranslateX(arg1);
}

void MainWindow::on_spinBox_3_valueChanged(int arg1)
{
    ui->widget->setTranslateY(arg1);
}

.ui文件

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>721</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0" rowspan="2">
     <widget class="TestWidget" name="widget" native="true">
      <property name="minimumSize">
       <size>
        <width>500</width>
        <height>0</height>
       </size>
      </property>
      <property name="styleSheet">
       <string notr="true"/>
      </property>
     </widget>
    </item>
    <item row="0" column="1" rowspan="2">
     <layout class="QVBoxLayout" name="verticalLayout_2">
      <item>
       <layout class="QHBoxLayout" name="horizontalLayout">
        <item>
         <widget class="QLabel" name="label">
          <property name="text">
           <string>Angle</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QSpinBox" name="spinBox">
          <property name="suffix">
           <string>°</string>
          </property>
          <property name="maximum">
           <number>360</number>
          </property>
          <property name="singleStep">
           <number>10</number>
          </property>
         </widget>
        </item>
       </layout>
      </item>
      <item>
       <layout class="QHBoxLayout" name="horizontalLayout_2">
        <item>
         <widget class="QLabel" name="label_2">
          <property name="text">
           <string>x轴</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QSpinBox" name="spinBox_2">
          <property name="minimum">
           <number>-250</number>
          </property>
          <property name="maximum">
           <number>250</number>
          </property>
          <property name="singleStep">
           <number>10</number>
          </property>
         </widget>
        </item>
       </layout>
      </item>
      <item>
       <layout class="QHBoxLayout" name="horizontalLayout_3">
        <item>
         <widget class="QLabel" name="label_3">
          <property name="text">
           <string>y轴</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QSpinBox" name="spinBox_3">
          <property name="minimum">
           <number>-250</number>
          </property>
          <property name="maximum">
           <number>250</number>
          </property>
          <property name="singleStep">
           <number>10</number>
          </property>
         </widget>
        </item>
       </layout>
      </item>
      <item>
       <spacer name="verticalSpacer">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>20</width>
          <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>721</width>
     <height>22</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <customwidgets>
  <customwidget>
   <class>TestWidget</class>
   <extends>QWidget</extends>
   <header>mainwindow.h</header>
   <container>1</container>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

参考资料

  1. 数学基础–矩阵
  2. OpenGL,Qt实现:1入门篇(缩放,位移,旋转)

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

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

相关文章

6 -【Faster R-CNN 代码精读】之 Proposals 、 Filter Proposals

6 -【Faster R-CNN 代码精读】之 Proposals 、 Filter Proposals1、前言2、数据回顾3、计算候选框位置&#xff08;proposal coordinates&#xff09;4、筛选候选框&#xff08;filter proposals&#xff09;及相关处理1&#xff09;筛选出 预测概率 排前 2000 的proposals2&am…

TCP协议面试灵魂12 问(四)

005: 介绍一下 TCP 报文头部的字段 报文头部结构如下(单位为字节): 请大家牢记这张图&#xff01; 源端口、目标端口 如何标识唯一标识一个连接&#xff1f;答案是 TCP 连接的四元组——源 IP、源端口、目标 IP 和目标端口。 那 TCP 报文怎么没有源 IP 和目标 IP 呢&#x…

2021年下半年信息系统项目管理师《综合知识》《案例分析》《论文》真题与答案

1、“十四五”期间&#xff0c;我国关注推动政务信息化共建共用、推动构建网络空间命运共同体&#xff0c;属于()的建设内容.A、科技中国 B、数字中国 C、制造强国 D、创新强国0参考答案&#xff1a;B2、()关注的是业务&#xff0c;以业务驱动技术&#xff0c;强调IT与业务的对…

零基础学FPGA(八):可编程逻辑单元(基本结构,Xilinx+Altera)

目录日常唠嗑一、概述二、基于多路选择器的逻辑单元1、基于多路选择器的逻辑单元&#xff08;早期&#xff09;2、基于PLD结构的逻辑单元&#xff08;类CPLD&#xff09;3、基于查询表的逻辑单元&#xff08;目前主流&#xff09;三、Xilinx基本结构四、Altera 基本结构日常唠嗑…

Java语言还能火多久? 还能选择Java开发吗?

​​整个互联网行业“不进则退&#xff0c;慢进亦退”。对于用人要求持续增高的互联网企业来说&#xff0c;中高级Java程序员才是当下市场最紧缺的。 现在的你&#xff0c;是十年前你的决定&#xff0c;十年后的你&#xff0c;是现在你的决定。选择很重要 为什么选择Java开发…

代码随想录算法训练营第六十天_第九章_动态规划 | 647. 回文子串、516.最长回文子序列、动态规划总结篇

LeetCode 647. 回文子串 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。具有不同开始位置或结束位置的子串&#xff0c;即使是由相同的字符组成&#xff0c;也会被视作不同的子串。 视频讲解https://www.bilibili.com/video/BV17G4y1y7z9/?spm_i…

Linux 进程知识总结

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Linux操作…

高阶数据结构之哈希的应用

文章目录位图&#xff08;bitMap&#xff09;位图的实现将数据添加到位图中检查数据是否在位图中存在将数据的对应位置置为0位图的应用布隆过滤器为什么会有误差布隆过滤器的实现布隆过滤器的删除使用Google下的guava组件操作布隆过滤器布隆过滤器的缺陷布隆过滤器的使用场景海…

分享69个ASP源码,总有一款适合您

分享69个ASP源码&#xff0c;总有一款适合您 69个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1XXwBL0Y0nOSel9xJVqz0Ow?pwdertw 提取码&#xff1a;ertw Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj base_url "https://d…

关于JavaScript编译原理以及作用域的深入探讨

前言 几乎所有编程语言最基本的功能之一&#xff0c;就是能够储存变量当中的值&#xff0c;并且能在之后对这个值进行访问或修改。事实上&#xff0c;正是这种储存和访问变量的值的能力将状态带给了程序。 若没有了状态这个概念&#xff0c;程序虽然也能够执行一些简单的任务…

高码率QPSK调制解调方案(2.1 QPSK调制)

2 全数字高码率QPSK调制解调软件设计 2.1 QPSK调制 2.1.1 QPSK调制原理 QPSK调制即正交相移键控信号,其信号表达式为 (1) 其中,和分别表示每比特的能量和持续

​LabView​动态改变显示文本框的属性(颜色、字体)

目录 问题&#xff1a; 解决方式 1、在程序框图面板上&#xff0c;选择显示文本框&#xff0c;右键创建属性节点 2、选择属性节点为数值文本&#xff0c;依据需要改变的类型选择文本颜色或字体 3、依据下位机上传的数值大小&#xff0c;选择-条件结构类型&#xff0c;依据…

03俯瞰全局:gRPC是如何进行通信的

gRPC作为Web网站和APP后端的最常用服务提供方式之一和分布式微服务架构不同节点间的最常见通信方式之一,它自身是如何进行通信的呢,这个问题就是我们本篇文章将要研究的重点,先从全局的角度分析gRPC服务端和客户端进行通信的主要流程,再从细节剖析gRPC是如何进行高效的远程…

Qt QVector “isDetached()“

文章目录摘要尝试通过加锁解决lock&unlockQMutexLockertry_lock改变量属性Q_ASSERT参考关键字&#xff1a; ASSERT、 isDetached、 崩溃、 QVector、 Detach摘要 今天在公司填坑的时候&#xff0c;有随机获得了一个新的BUG&#xff0c;就是一直报ASSERT&#xff1a;”isD…

剑指 Offer 35. 复杂链表的复制

题目 请实现 copyRandomList 函数&#xff0c;复制一个复杂链表。在复杂链表中&#xff0c;每个节点除了有一个 next 指针指向下一个节点&#xff0c;还有一个 random 指针指向链表中的任意节点或者 null。 思路 方法一&#xff1a;哈希表 利用哈希表的查询特点&#xff…

【Linux】项目自动化构建工具-make/Makefile与Linux调试器-gdb使用

文章目录Linux项目自动化构建工具-make/MakefileLinux调试器-gdb使用Linux项目自动化构建工具-make/Makefile 背景 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录…

SpringBoot+Vue图书馆管理系统

简介&#xff1a;本项目采用了基本的SpringBootVue设计的图书馆管理系统。详情请看截图。经测试&#xff0c;本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 项目描述 项目名称SpringBootVue图书馆管理系统源码作者LHL项目类型Java EE项目 &#xff08;…

【iMessage苹果推推送】将看到一个可扩大选项“AppleDevelopmentPushServices”6.扩展此选项并右键单击“Appledge”>

推荐内容IMESSGAE相关 作者✈️IMEAX推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容3.日历推 *** …

基于NOSTR协议的“公有制”版本的Twitter,去中心化社交软件Damus用后感,一个极端走向另一个极端

最近&#xff0c;一个幽灵&#xff0c;Web3的幽灵&#xff0c;在网络游荡&#xff0c;它叫Damus&#xff0c;这玩意诠释了什么叫做病毒式营销&#xff0c;滑稽的是&#xff0c;一个Web3产品却在Web2的产品链上疯狂传销&#xff0c;各方大佬纷纷为其背书&#xff0c;到底发生了什…

基于python实现疫情期间微博、B站网民情绪分析

一项目概述&#xff1a;“疫情下的社会心理学”这一课题旨在通过疫情期间大众在自媒体上的新闻评论等信息&#xff0c;凭借一些方法分析出总体的心理变化和情绪走向&#xff0c;并在宏观上把握了总体的心态格局。对于该课题&#xff0c;我们小组首先爬取了哔哩哔哩和微博的大量…