当前位置: 首页 > news >正文

FFmpeg与OpenCV联合开发

本文讲述如何利用FFmpeg SDK与OpenCV 从RTSP流中获取图像(OpenCV MAT 对象格式)。

一,构造RTSP视频流

因为是在本机实验,所以我自己构造了一个RTSP流。如果你有现成的RTSP流也可以的。
实验用的源视频是黑神话·悟空的《云宫讯音》。
在这里插入图片描述
自己产生一个RTSP视频流得需要一个RTSP流媒体服务器。目前可以用的RTSP流媒体服务器有两个,分别是ZLMediaKit和MediaMTX两个。ZLMediaKit需要编译,我编译好了。MediaMTX下载即用。这里我选了MediaMTX,下载下来就可以用,比较轻量化。

1,启动流媒体服务器

在MediaMTX的安装目录下启动即可。
在这里插入图片描述

2,启动FFmpeg推流

ffmpeg -re -stream_loop -1 -i “wukong.mp4” -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://192.168.76.189:8554/live/test
在这里插入图片描述
这是流的相关信息:
Input #0, rtsp, from ‘rtsp://192.168.76.189:8554/live/test’:f=0/0
Metadata:
title : No Name
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080 [SAR 1:1 DAR 16:9], 30 fps, 30 tbr, 90k tbn
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp

3,用FFplay播放一下

在这里插入图片描述

二,源代码

#include <stdio.h>
#include <thread>
#include <string>
//#include <io.h>
//#include <vector>//#define __STDC_CONSTANT_MACROS
#include <iostream>
using namespace std;
extern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libavutil/time.h"
//#include "libavutil/log.h"
//#include "libavutil/mathematics.h"
#include "libswscale/swscale.h"
//#include "libavutil/imgutils.h"
}#include <opencv2/opencv.hpp>using namespace cv;int main(int argc, char* argv[])
{Mat pCvMat;//const string sourceWindow = "test";//namedWindow(sourceWindow,1);//cout<<avcodec_version()<<endl;//cout<<FFMPEG_VERSION<<endl;unsigned codecVer = avcodec_version();int ver_major, ver_minor, ver_micro;ver_major = (codecVer >> 16) & 0xff;ver_minor = (codecVer >> 8) & 0xff;ver_micro = (codecVer) & 0xff;printf("Current ffmpeg version is:    ,avcodec version is: %d=%d.%d.%d\n",codecVer, ver_major, ver_minor, ver_micro);int startTime = 0;                           // 记录播放开始int currentFrame = 0;                           // 当前帧序号double fps = 0;                                 // 帧率double interval = 0;                            // 帧间隔const AVInputFormat* p_ifmt_v = NULL;// ffmpeg相关变量预先定义与分配AVFormatContext* pAVFormatContext = 0;          // ffmpeg的全局上下文,所有ffmpeg操作都需要AVStream* pAVStream = 0;                        // ffmpeg流信息AVCodecContext* pAVCodecContext = avcodec_alloc_context3(NULL);            // ffmpeg编码上下文const AVCodec* pAVCodec = 0;                          // ffmpeg编码器AVPacket* pAVPacket = 0;                        // ffmpag单帧数据包AVFrame* pAVFrame = 0;                          // ffmpeg单帧缓存//AVFrame *pAVFrameBGR24 = 0;                     // ffmpeg单帧缓存转换颜色空间后的缓存struct SwsContext* pSwsContext = 0;             // ffmpeg编码数据格式转换AVDictionary* pAVDictionary = 0;                // ffmpeg数据字典,用于配置一些编码器属性等//unsigned char * outBuffer = 0;                           // 解码后的数据存放缓存区//const char* url_v = "video.sdp";int ret = 0;                                    // 函数执行结果int videoIndex = -1;                            // 音频流所在的序号//int numBytes = 0;                               // 解码后的数据长度pAVFormatContext = avformat_alloc_context();    // 分配//pAVFormatContext->flags |= AVFMT_NOFILE;//添加白名单,这里很重要,如果不申请内存,在avformat_close_input中会宕//pAVFormatContext->protocol_whitelist = (char*)av_malloc(sizeof("file,udp,rtp"));//memcpy(pAVFormatContext->protocol_whitelist, "file,udp,rtp", sizeof("file,udp,rtp"));//pAVPacket = av_packet_alloc();                  // 分配pAVPacket = (AVPacket*)av_malloc(sizeof(AVPacket));pAVFrame = av_frame_alloc();                    // 分配//pAVFrameBGR24 = av_frame_alloc();               // 分配p_ifmt_v = av_find_input_format("rtsp");//if(!pAVFormatContext || !pAVPacket || !pAVFrame || !pAVFrameBGR24)if (!pAVFormatContext || !pAVPacket || !pAVFrame){cout << "Failed to alloc" << endl;system("pause");return -1;}// 步骤一:注册所有容器和编解码器(也可以只注册一类,如注册容器、注册编码器等)//av_register_all();avformat_network_init();// 步骤二:打开文件(ffmpeg成功则返回0)//std::string src = "rtsp://192.168.76.215/live/test";std::string src = argv[1];cout << "打开:" << src << endl;AVDictionary* format_opts = NULL;av_dict_set(&format_opts, "rtsp_transport", "tcp", 0); //设置推流的方式,默认udp。ret = avformat_open_input(&pAVFormatContext, src.c_str(), p_ifmt_v, &format_opts);//nullptr//ret = avformat_open_input(&pAVFormatContext, src.c_str(), p_ifmt_v, nullptr);if (ret){cout << "Failed" << endl;system("pause");return -1;}// 步骤三:探测流媒体信息ret = avformat_find_stream_info(pAVFormatContext, 0);if (ret < 0){cout << "Failed to avformat_find_stream_info(pAVFormatContext, 0)" << endl;system("pause");return -1;}printf("******************\n");av_dump_format(pAVFormatContext, 0, src.c_str(), 0);printf("******************\n");// 步骤四:提取流信息,提取视频信息for (int index = 0; index < pAVFormatContext->nb_streams; index++){if (pAVFormatContext->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){cout << "流序号:" << index << "\n类型为:" << "AVMEDIA_TYPE_VIDEO" << endl;//pAVCodecContext = pAVFormatContext->streams[index]->codecpar->;avcodec_parameters_to_context(pAVCodecContext, pAVFormatContext->streams[index]->codecpar);pAVStream = pAVFormatContext->streams[index];videoIndex = index;break;}}if (videoIndex == -1 || !pAVCodecContext){cout << "Failed to find video stream" << endl;system("pause");return -1;}// 步骤五:对找到的视频流寻解码器pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);if (!pAVCodec){cout << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"<< pAVCodecContext->codec_id << endl;system("pause");return -1;}// 步骤六:打开解码器// 设置缓存大小 1024000byteav_dict_set(&pAVDictionary, "buffer_size", "8192000", 0);// 设置超时时间 20sav_dict_set(&pAVDictionary, "stimeout", "20000000", 0);// 设置最大延时 3sav_dict_set(&pAVDictionary, "max_delay", "30000000", 0);// 设置打开方式 tcp/udpav_dict_set(&pAVDictionary, "rtsp_transport", "tcp", 0);//ret = avcodec_open2(pAVCodecContext, pAVCodec, &pAVDictionary);ret = avcodec_open2(pAVCodecContext, pAVCodec, &pAVDictionary);if (ret){cout << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)" << endl;system("pause");return -1;}// 显示视频相关的参数信息(编码上下文)cout << "比特率:" << pAVCodecContext->bit_rate << endl;cout << "宽高:" << pAVCodecContext->width << "x" << pAVCodecContext->height << endl;cout << "格式:" << pAVCodecContext->pix_fmt << endl;  // AV_PIX_FMT_YUV420P 0cout << "帧率分母:" << pAVCodecContext->time_base.den << endl;cout << "帧率分子:" << pAVCodecContext->time_base.num << endl;cout << "帧率分母:" << pAVStream->avg_frame_rate.den << endl;cout << "帧率分子:" << pAVStream->avg_frame_rate.num << endl;cout << "总时长:" << pAVStream->duration / 10000.0 << "s" << endl;cout << "总帧数:" << pAVStream->nb_frames << endl;// 有总时长的时候计算帧率(较为准确)//    fps = pAVStream->nb_frames / (pAVStream->duration / 10000.0);//    interval = pAVStream->duration / 10.0 / pAVStream->nb_frames;// 没有总时长的时候,使用分子和分母计算fps = pAVStream->avg_frame_rate.num * 1.0f / pAVStream->avg_frame_rate.den;interval = 1 * 1000 / fps;cout << "平均帧率:" << fps << endl;cout << "帧间隔:" << interval << "ms" << endl;// 步骤七:对拿到的原始数据格式进行缩放转换为指定的格式高宽大小pAVCodecContext->pix_fmt = (AVPixelFormat)AV_PIX_FMT_YUV420P;pSwsContext = sws_getContext(pAVCodecContext->width,pAVCodecContext->height,pAVCodecContext->pix_fmt,pAVCodecContext->width,pAVCodecContext->height,AV_PIX_FMT_BGR24,SWS_FAST_BILINEAR,0,0,0);cout << "sws_getContext!" << endl;pCvMat = Mat(pAVCodecContext->height, pAVCodecContext->width, CV_8UC3);int cvLinesizes[1];cvLinesizes[0] = pCvMat.step1();// 步骤八:读取一帧数据的数据包//av_free_packet(pAVPacket);//av_packet_unref(pAVPacket);int frame_count = 0;//cout << "now av_read_frame!" << endl;//namedWindow("img", 0);while (av_read_frame(pAVFormatContext, pAVPacket) >= 0){if (pAVPacket->stream_index == videoIndex){//cout << "av_read_frame!" << endl;// 步骤八:对读取的数据包进行解码ret = avcodec_send_packet(pAVCodecContext, pAVPacket);//ret = avcodec_decode_video2(pAVCodecContext, pAVFrame, &got_picture, pAVPacket);if (ret){cout << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket) ,ret ="<< ret << endl;break;}//cout << "avcodec_send_packet!" << endl;av_frame_unref(pAVFrame);while (!avcodec_receive_frame(pAVCodecContext, pAVFrame)){//pCvMat.data=NULL;sws_scale(pSwsContext,(const uint8_t* const*)pAVFrame->data,pAVFrame->linesize,0,pAVCodecContext->height,&pCvMat.data,cvLinesizes);//cout << "avcodec_receive_frame!" << endl;frame_count++;if (frame_count % int(25) == 0) {//sprintf()//char * temp;//itoa(frame_count,temp,10);//string sss=temp;imwrite("./image/test_" + std::to_string(frame_count) + ".bmp", pCvMat);//imshow("img", pCvMat);cout << "SAVE: " << std::to_string(frame_count) << endl;}//threshold(pCvMat,pCvMat,128,255,THRESH_BINARY);//imshow(sourceWindow,pCvMat);//waitKey(1);av_frame_unref(pAVFrame);}// 下一帧currentFrame++;while (av_gettime() - startTime < currentFrame * interval){//Sleep(1);std::this_thread::sleep_for(std::chrono::milliseconds(1));}//cout << "current:" << currentFrame <<"," << time << av_gettime()- startTime <<endl;}//av_free_packet(pAVPacket);av_packet_unref(pAVPacket);}cout << "释放回收资源" << endl;if (pSwsContext){sws_freeContext(pSwsContext);pSwsContext = 0;cout << "sws_freeContext(pSwsContext)" << endl;}if (pAVFrame){av_frame_free(&pAVFrame);pAVFrame = 0;cout << "av_frame_free(pAVFrame)" << endl;}if (pAVPacket){//av_free_packet(pAVPacket);pAVPacket = 0;cout << "av_free_packet(pAVPacket)" << endl;}if (pAVCodecContext){avcodec_free_context(&pAVCodecContext);avcodec_close(pAVCodecContext);pAVCodecContext = 0;cout << "avcodec_close(pAVCodecContext);" << endl;}if (pAVFormatContext){avformat_close_input(&pAVFormatContext);avformat_free_context(pAVFormatContext);pAVFormatContext = 0;cout << "avformat_free_context(pAVFormatContext)" << endl;}pCvMat = NULL;system("pause");return 0;
}

三,运行一下

TestFFmpegOpencv_RTSP.exe rtsp://192.168.76.189:8554/live/test
在这里插入图片描述

四,结果

提取出的帧结果保存在image文件夹下:
在这里插入图片描述

相关文章:

FFmpeg与OpenCV联合开发

本文讲述如何利用FFmpeg SDK与OpenCV 从RTSP流中获取图像&#xff08;OpenCV MAT 对象格式&#xff09;。 一&#xff0c;构造RTSP视频流 因为是在本机实验&#xff0c;所以我自己构造了一个RTSP流。如果你有现成的RTSP流也可以的。 实验用的源视频是黑神话悟空的《云宫讯音》…...

Docker 部署 Redis (图文并茂超详细)

部署 Redis ( Docker ) [Step 1] : 拉取 Redis 镜像, 推荐使用 7 的 Redis 版本 docker pull redis:7.0.12[Step 2] : 创建 Redis 相关目录 ➡️ 启动 Redis 容器 ➡️ 拷贝文件 ➡️ 授权文件夹 ➡️ 删除容器 # 创建 Redis 相关目录 mkdir -p /data/redis/{conf,data,log…...

Docker基础-Docker Compose使用

文章目录 一、什么是Docker Compose?常用命令 Docker Compose安装前提条件Linux 上的安装macOS 上的安装Windows 上的安装注意事项启动 Docker Compose 服务 Docker Compose使用Docker Compose 文件基本结构关键概念示例&#xff1a;更复杂的配置注意事项 参考文献 一、什么是…...

GPT撰写开题报告教程——课题确定及文献调研

撰写开题报告是一项复杂而重要的任务&#xff0c;需要涵盖从主题选择到文献综述、研究方法等多个环节。借助AI&#xff0c;如ChatGPT&#xff0c;可以显著提高这一过程的效率以及内容的质量。本文将详细探讨如何一步步利用ChatGPT撰写开题报告。 一、开题报告内容 一个清晰的…...

SprinBoot+Vue高校就业管理系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…...

【人工智能】Transformers之Pipeline(十八):文本生成(text-generation)

目录 一、引言 二、文本生成&#xff08;text-generation&#xff09; 2.1 概述 2.2 家谱树谱——encoder or decoder is a problem 2.3 pipeline参数 2.3.1 pipeline对象实例化参数 2.3.2 pipeline对象使用参数 ​​​​​​​ 2.3.3 pipeline返回参数 ​​​​​​​…...

判断当前用户登录时常是否超过两个小时

有一个签到功能需要用户登录时间超过两个小时才可以签到 1. 存储登录时间戳 login() { // 假设这是登录后的操作 const currentTime new Date().getTime(); // 获取当前时间戳 localStorage.setItem(loginTimestamp, currentTime.toString()); // 存储登录时间戳 // 其他…...

nacos明明配置了远程连接地址却一直连接本地的详细配置解释

大家时间都很珍贵&#xff0c;我直接把方法放这 这个是yml文件&#xff0c;我们配置yml文件的时候&#xff0c;一定要把他的服务发现地址写了 这里是针对bootstrap做出的文件&#xff0c;注意名字&#xff0c;要和我们在yml文件里面的spring名字一样 yml discovery:是发现的意…...

Superset二次开发之源码 run-server.sh 分析

背景&#xff1a; 如果基于docker方式部署Superset项目&#xff0c;Dockerfile文件末尾指向了docker-ci.sh&#xff0c;而docker-ci.sh 脚本又指向了run-server.sh。因此我们重点分析一下run-server脚本 路径 docker\run-server.sh #!/usr/bin/env bashHYPHEN_SYMBOL-gunicorn…...

Java 之四种内部类详解

在 Java 中&#xff0c;除了传统的类定义方式之外&#xff0c;还有一种特殊的类定义方式——内部类。内部类定义在另一个类的内部&#xff0c;可以访问外部类的成员&#xff0c;这使得代码更加灵活和可读性更强。本文将详细讲解 Java 中四种内部类的类型&#xff1a;成员内部类…...

03:手动可变电阻

可变电阻 1、电位器2、变阻器/数字电位器2.1&#xff1a;变阻器2.2&#xff1a;数字电位器 3、电位器锥度4、寄生电感/电容 1、电位器 如上图所示&#xff1a;将可变的电阻作为分压器&#xff0c;那么这种可变的电阻就是电位器。例如&#xff1a;将L1连接负极&#xff0c;L3连接…...

嵌入式Linux电池管理(TODO)

&#xff08;TODO&#xff09; 在树莓派3B上使用电池供电需要考虑多个方面&#xff0c;包括电源管理、硬件连接和软件配置。以下是详细的步骤和建议&#xff1a; 1. 选择合适的电池 树莓派3B需要5V的电源供电&#xff0c;通常电流需求在2.5A左右。常见的电池选择包括&#xff1…...

Python 求亲和数

亲和数&#xff08;Amicable Numbers&#xff09;是指两个不同的正整数&#xff0c;它们的真因数&#xff08;即除去本身的所有因数&#xff09;之和与对方的数相等。 def sum_of_proper_divisors(n):"""计算一个数的真因子之和"""divisors_su…...

【C++】——vector模拟实现和迭代器失效问题

文章目录 模拟实现vector基本成员变量vector的构造与析构vector迭代器vector容量vector元素访问vector修改操作 vector迭代器失效问题什么是迭代器失效1.插入元素导致迭代器失效2.删除元素导致迭代器失效3.重新分配空间导致迭代器失效 如何解决迭代器失效问题 模拟实现 vector…...

USB 3.1 标准 A 型连接器及其引脚分配

USB 3.1 标准 A 型连接器 USB 3.1 标准 A 型连接器被定义为主机连接器。它具有与 USB 2.0 标准 A 型连接器相同的配合接口&#xff0c;但增加了另外两对差分信号和一个接地引脚。 USB 3.1 标准 A 型插座可以接受 USB 3.1 标准 A 型插头或 USB 2.0 标准 A 型插头。类似地&…...

机器学习文献|基于循环细胞因子特征,通过机器学习算法预测NSCLC免疫治疗结局

今天我们一起学习一篇最近发表在Journal for immunotherapy of cancer &#xff08;IF 10.9&#xff09;上的文章&#xff0c;Machine learning for prediction of immunotherapeutic outcome in non-small-cell lung cancer based on circulating cytokine signatures[基于循环…...

Qt 实现自定义截图工具

目录 Qt 实现自定义截图工具实现效果图PrintScreen 类介绍PrintScreen 类的主要特性 逐步实现第一步&#xff1a;类定义第二步&#xff1a;初始化截图窗口第三步&#xff1a;处理鼠标事件第四步&#xff1a;计算截图区域第五步&#xff1a;捕获和保存图像 完整代码PrintScreen.…...

第15-05章:获取运行时类的完整结构

我的后端学习大纲 我的Java学习大纲 6.1.第一组方法API: 1.API列表&#xff1a;java.lang.Class 类&#xff1a; 2.代码测试&#xff1a; public class ReflectionUtils{ puvblic static void main(String[] args){}// 第一组Testpublic void api_01{//上面截图的代码......…...

【Kubernetes】K8s 的鉴权管理(二):基于属性 / 节点 / Webhook 的访问控制

K8s 的鉴权管理&#xff08;二&#xff09;&#xff1a;基于属性 / 节点 / Webhook 的访问控制 1.基于属性的访问控制&#xff08;ABAC 鉴权&#xff09;2.基于节点的访问控制&#xff08;node 鉴权&#xff09;2.1 读取操作2.2 写入操作 3.基于 Webhook 的访问控制3.1 基于 We…...

Java面试、技巧、问题、回复,资源面面观

入门 先了解一下面试流程 复习 Java 基础知识&#xff1a; 温习 Java 编程的核心概念&#xff0c;包括数据类型、变量、循环、数组和面向对象的编程原则。数据结构和算法&#xff1a; 加强您对 Java 编程中使用的基本数据结构和算法的理解。练习编码&#xff1a; 在各种平台上解…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...

相关类相关的可视化图像总结

目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系&#xff0c;可直观判断线性相关、非线性相关或无相关关系&#xff0c;点的分布密…...