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

OpenCV分水岭算法watershed函数的使用

  • 操作系统:ubuntu22.04
  • OpenCV版本:OpenCV4.9
  • IDE:Visual Studio Code
  • 编程语言:C++11

描述

我们将学会使用基于标记的分水岭算法来进行图像分割。我们将看到:watershed()函数的用法。
        任何灰度图像都可以被视为一个地形表面,其中高强度对应着山峰和丘陵,而低强度则对应着山谷。你可以想象,从每个孤立的山谷(局部最小值)开始,用不同颜色的水(标记)来填充。随着水位上升,依据附近的山峰(梯度),来自不同山谷的水,显然带有不同的颜色,将会开始融合。为了避免这种情况发生,你必须在水开始汇合的地方建立起屏障。你持续进行填充水和构建屏障的工作,直到所有的山峰都被水覆盖。此时,你所建立的这些屏障就构成了分割的结果。这就是分水岭算法背后的理念。你可以在CMM网页上关于分水岭的页面,通过观看一些动画来更直观地理解这个概念。

        但是,这种方法会因为图像中的噪声或其他不规则性而导致过度分割的结果。因此,OpenCV实现了一种基于标记的分水岭算法,其中你指明了哪些山谷点应该被合并,哪些不应该。这是一种交互式的图像分割方式。我们所做的就是给已知的对象赋予不同的标记。将我们确信属于前景或对象的区域标记为一种颜色(或强度),将我们确信属于背景或非对象的区域标记为另一种颜色,最后,对于那些我们不确定的区域,我们将其标记为0。这就是我们的标记。接着,应用分水岭算法。随后,我们的标记将被更新为我们给予的标签,而对象的边界将拥有一个值为-1的特殊标记。

代码

假设我们有一张硬币的图像,其中硬币彼此接触。即使你对图像进行了阈值处理,硬币的边缘仍然会粘连在一起,原图如下:
在这里插入图片描述
我们开始着手于对硬币数量进行一个大致的估算。为此,我们可以使用大津的二值化方法(Otsu’s binarization)。

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <cstdio>
#include <iostream>
#include <opencv2/core/utility.hpp>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img = imread( "/media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg", 1 ), imgGray;if ( img.empty() ){cout << "Couldn't open image " << std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV+cv::THRESH_OTSU );cv::imshow( "Original Image", img );cv::imshow( "Gray Image", imgGray );cv::imshow( "binary Image", binary );cv::waitKey( 0 );return 0;
}

运行结果:
在这里插入图片描述
现在我们需要去除图像中的任何细小的白色噪声。为此,我们可以使用形态学开运算。为了消除物体上的任何微小孔洞,我们可以使用形态学闭运算。因此,我们现在可以确信,靠近物体中心的区域是前景,而远离物体的区域则是背景。唯一不确定的区域是硬币的边界区域。

所以我们需要提取那些我们确信是硬币的区域。腐蚀操作可以移除边界像素。因此,剩下的区域,我们可以确信那就是硬币。这在物体彼此不接触的情况下是可行的。但由于它们相互接触,另一个好的选择是找到距离变换并应用一个适当的阈值。接下来我们需要找出那些我们确信不是硬币的区域。为此,我们对结果进行膨胀处理。膨胀操作会使物体边界扩展到背景。这样一来,我们就可以确保结果中处于背景中的任何区域确实是背景,因为边界区域已经被去除了。请参见下图。
在这里插入图片描述

剩余的区域是我们无法确定是硬币还是背景的部分。这些不确定区域通常位于硬币边界处,也就是前景与背景相遇的地方(甚至可能是两个不同硬币相遇的区域)。我们称这部分区域为边界区域。边界区域可以通过从确定的背景区域(sure_bg)中减去确定的前景区域(sure_fg)得到。


#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <cstdio>
#include <iostream>
#include <opencv2/core/utility.hpp>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img = imread( "/media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg", 1 ), imgGray;if ( img.empty() ){cout << "Couldn't open image " << std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU );// noise removalcv::Mat kernel = cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 );  // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate(opening, sure_bg, kernel, cv::Point(-1,-1), 3); // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform(opening, dist_transform, cv::DIST_L2, 3);cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc(dist_transform, nullptr, &maxVal);// 设置阈值double thresholdValue = 0.7 * maxVal;cv::threshold(dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY);//  Finding unknown regionsure_fg.convertTo(sure_fg, CV_8U);cv::Mat unknown;// 执行矩阵相减操作cv::subtract(sure_bg, sure_fg, unknown);// cv::imshow( "原始图", img );// cv::imshow( "灰度图", imgGray );// cv::imshow( "二值化后的图", binary );cv::imshow( "sure_fg", sure_fg );cv::imshow( "dist_transform", dist_transform );cv::waitKey( 0 );return 0;
}

在阈值处理后的图像中,如下图,我们可以看到一些硬币区域,我们确信这些区域属于硬币,并且它们现在是分离的。在某些情况下,你可能只对前景分割感兴趣,而不关心相互接触的物体是否分离。在这种情况下,你不需要使用距离变换,仅仅使用腐蚀操作就足够了。腐蚀操作其实只是另一种提取确定前景区域的方法,仅此而已。
在这里插入图片描述
现在我们已经确定了哪些区域属于硬币,哪些属于背景。因此,我们可以创建一个标记(marker)图像,它与原始图像具有相同的尺寸,但数据类型为int32。在这个标记图像中,我们将确定的区域(无论是前景还是背景)标记为不同的正整数,而不确定的区域则保持为零。

在OpenCV中,我们可以使用cv::connectedComponentsWithStats函数来实现这一目的。该函数会将图像的背景标记为0,其他对象则从1开始分配不同的整数标签。然而,正如你所提到的,如果背景被标记为0,那么在Watershed算法中,它将被视为未知区域。为了避免这种情况,我们应该将未知区域,即由unknown定义的区域,标记为0,而将背景标记为一个不同的整数。

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <cstdio>
#include <iostream>
#include <opencv2/core/utility.hpp>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img = imread( "/media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg", 1 ), imgGray;if ( img.empty() ){cout << "Couldn't open image " << std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU );// noise removalcv::Mat kernel = cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 );  // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate( opening, sure_bg, kernel, cv::Point( -1, -1 ), 3 );  // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform( opening, dist_transform, cv::DIST_L2, 3 );cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc( dist_transform, nullptr, &maxVal );// 设置阈值double thresholdValue = 0.7 * maxVal;cv::threshold( dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY );//  Finding unknown regionsure_fg.convertTo( sure_fg, CV_8U );cv::Mat unknown;// 执行矩阵相减操作cv::subtract( sure_bg, sure_fg, unknown );// Marker labellingcv::Mat markers;  // 将会存储标记结果// 执行连通组件标记int num_labels = cv::connectedComponents( sure_fg, markers );cv::Mat ones = cv::Mat::ones( markers.size(), markers.type() );// 将 markers 矩阵的所有元素值增加1cv::add( markers, ones, markers );// 创建一个与 markers 大小相同的掩码矩阵,其中 unknown 矩阵中值为255的位置为 true,其余位置为 falsecv::Mat mask = unknown == 255;// 将 markers 矩阵中对应于 mask 矩阵中 true 的位置的元素设置为0markers.setTo( 0, mask );// 创建一个与原图像大小相同的输出图像cv::Mat colorImage;// 将灰度图像转换为具有Jet色彩映射的彩色图像cv::applyColorMap(mask, colorImage, cv::COLORMAP_JET);//  Add one to all labels so that sure background is not 0, but 1// cv::imshow( "原始图", img );// cv::imshow( "灰度图", imgGray );// cv::imshow( "二值化后的图", binary );cv::imshow( "sure_fg", sure_fg );cv::imshow( "dist_transform", dist_transform );cv::imshow( "mask", colorImage );cv::waitKey( 0 );return 0;
}

在应用了JET色彩映射的结果中,红色区域代表了未知区域,这是在硬币分割过程中尚未确定为硬币或背景的部分。确定的硬币区域则被赋予了不同的色彩值。而确定为背景的区域则以较浅的蓝色显示,与未知区域的红色色形成对比。
在这里插入图片描述

现在我们的标记图像已经准备好了,下一步就是应用Watershed算法。一旦应用了Watershed算法,标记图像将会被修改。在硬币和背景之间的边界区域将会被标记为-1,这是OpenCV中Watershed算法的一个特性,它用-1来表示分割出的边界区域。

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <cstdio>
#include <iostream>
#include <opencv2/core/utility.hpp>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{Mat img = imread( "/media/dingxin/data/study/OpenCV/sources/images/water_coins.jpg", 1 ), imgGray;if ( img.empty() ){cout << "Couldn't open image " << std::endl;return 0;}cvtColor( img, imgGray, COLOR_BGR2GRAY );// 二值化图像cv::Mat binary;cv::threshold( imgGray, binary, 150, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU );// noise removalcv::Mat kernel = cv::Mat::ones( 3, 3, CV_8UC1 ) * 255;// 执行开运算cv::Mat opening;cv::morphologyEx( binary, opening, cv::MORPH_OPEN, kernel, cv::Point( -1, -1 ), 2 );  // 迭代次数为2cv::Mat sure_bg;// 执行膨胀操作cv::dilate( opening, sure_bg, kernel, cv::Point( -1, -1 ), 3 );  // 迭代次数为3cv::Mat dist_transform;// 执行距离变换cv::distanceTransform( opening, dist_transform, cv::DIST_L2, 3 );cv::Mat sure_fg;double maxVal;// 查找矩阵中的最大值cv::minMaxLoc( dist_transform, nullptr, &maxVal );// 设置阈值double thresholdValue = 0.7 * maxVal;cv::threshold( dist_transform, sure_fg, thresholdValue, 255, cv::THRESH_BINARY );//  Finding unknown regionsure_fg.convertTo( sure_fg, CV_8U );cv::Mat unknown;// 执行矩阵相减操作cv::subtract( sure_bg, sure_fg, unknown );// Marker labellingcv::Mat markers;  // 将会存储标记结果// 执行连通组件标记int num_labels = cv::connectedComponents( sure_fg, markers );cv::Mat ones = cv::Mat::ones( markers.size(), markers.type() );// 将 markers 矩阵的所有元素值增加1cv::add( markers, ones, markers );// 创建一个与 markers 大小相同的掩码矩阵,其中 unknown 矩阵中值为255的位置为 true,其余位置为 falsecv::Mat mask = unknown == 255;// 将 markers 矩阵中对应于 mask 矩阵中 true 的位置的元素设置为0markers.setTo( 0, mask );// 创建一个与原图像大小相同的输出图像cv::Mat colorImage;// 将灰度图像转换为具有Jet色彩映射的彩色图像cv::applyColorMap(mask, colorImage, cv::COLORMAP_JET);cv::imshow( "原始图", img );cv::watershed(img, markers);mask = markers == -1;img.setTo(cv::Scalar(255, 0, 0), mask);cv::imshow( "watershed", img );cv::waitKey( 0 );return 0;
}

在这里插入图片描述

相关文章:

OpenCV分水岭算法watershed函数的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 描述 我们将学会使用基于标记的分水岭算法来进行图像分割。我们将看到&#xff1a;watershed()函数的用法。 任何灰度图像都可以被视为一个地形表…...

laravel为Model设置全局作用域

如果一个项目中存在这么一个sql条件在任何情况下或大多数情况都会被使用&#xff0c;同时很容易被开发者遗忘&#xff0c;那么就非常适用于今天要提到的这个功能&#xff0c;Eloquent\Model的全局作用域。 首先看一个示例&#xff0c;有个数据表&#xff0c;结构如下&#xff1…...

Leetcode之string

目录 前言1. 字符串相加2. 仅仅反转字母3. 字符串中的第一个唯一字符4. 字符串最后一个单词的长度5. 验证回文串6. 反转字符串Ⅱ7. 反转字符串的单词Ⅲ8. 字符串相乘9. 打印日期 前言 本篇整理了一些关于string类题目的练习, 希望能够学以巩固. 博客主页: 酷酷学!!! 点击关注…...

OS:处理机进程调度

1.BackGround&#xff1a;为什么要进行进程调度&#xff1f; 在多进程环境下&#xff0c;内存中存在着多个进程&#xff0c;其数目往往多于处理机核心数目。这就要求系统可以按照某种算法&#xff0c;动态的将处理机CPU资源分配给处于就绪状态的进程。调度算法的实质其实是一种…...

【车辆轨迹处理】python实现轨迹点的聚类(一)——DBSCAN算法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、单辆车轨迹的聚类与分析1.引入库2.聚类3.聚类评价 二、整个数据集多辆车聚类1.聚类2.整体评价 前言 空间聚类是基于一定的相似性度量对空间大数据集进行分组…...

Apache Kylin

Apache Kylin 是一个开源的分布式分析引擎&#xff0c;提供 SQL 查询接口及多维分析&#xff08;OLAP&#xff09;能力以支持超大规模数据集。它能在亚秒级的时间内提供 PB 级数据的查询能力&#xff0c;非常适合大数据分析和报表系统。 ### 入门指南 #### 1. 环境准备 首先…...

为何Vue3比Vue2快

Proxy响应式 PatchFlag 编译模板时&#xff0c;动态节点做标记标记&#xff0c;分为不同的类型&#xff0c;如TEXT PROPSdiff算法时&#xff0c;可以区分静态节点&#xff0c;以及不同类型的动态节点 <div>Hello World</div> <span>{{ msg }}</span>…...

人工智能与社交变革:探索Facebook如何领导智能化社交平台

在过去十年中&#xff0c;人工智能&#xff08;AI&#xff09;技术迅猛发展&#xff0c;彻底改变了我们与数字世界互动的方式。Facebook作为全球最大的社交媒体平台之一&#xff0c;充分利用AI技术&#xff0c;不断推动社交平台的智能化&#xff0c;提升用户体验。本文将深入探…...

八股文之java基础

jdk9中对字符串进行了一个什么优化&#xff1f; jdk9之前 字符串的拼接通常都是使用进行拼接 但是的实现我们是基于stringbuilder进行的 这个过程通常比较低效 包含了创建stringbuilder对象 通过append方法去将stringbuilder对象进行拼接 最后使用tostring方法去转换成最终的…...

深度挖掘行情接口:股票市场中的关键金融数据API接口解析

在股票市场里&#xff0c;存在若干常见的股票行情数据接口&#xff0c;每一种接口皆具备独特的功能与用途。以下为一些常见的金融数据 API 接口&#xff0c;其涵盖了广泛的金融数据内容&#xff0c;其中就包含股票行情数据&#xff1a; 实时行情接口 实时行情接口&#xff1a…...

逆向破解 对汇编的 简单思考

逆向破解汇编非常之简单 只是一些反逆向技术非常让人难受 但网络里都有方法破解 申请变量 &#xff1a; int a 0; 00007FF645D617FB mov dword ptr [a],0 char b b; 00007FF645D61802 mov byte ptr [b],62h double c 0.345; 00007FF645D61…...

搜维尔科技:人机交互学术应用概览

人机交互学术应用概览 搜维尔科技&#xff1a;人机交互学术应用概览...

植物遗传转化相关介绍【卡梅德生物】

植物的遗传转化是指以植物器官、组织、细胞或原生质体作为受体&#xff0c;应用重组DNA技术&#xff0c;将外源基因导入植物基因组&#xff0c;以获得转基因植物的技术。目前应用最普遍的植物基因的遗传转化方法主要有农杆菌介导法和DNA直接转入法。 一&#xff0e;植物遗传转化…...

0711springNews新闻系统管理 实现多级评论

0611springmvc新闻系统管理-CSDN博客 0711springNews新闻系统管理项目包 实现多级评论-CSDN博客 数据库字段 需要添加父节点id&#xff0c;通过该字段实现父评论和子评论的关联关系。 对象属性 实现链表&#xff0c;通过一个父评论可以找到它对应的所有子孙评论。 业务层 实现…...

如何在Ubuntu上安装并启动SSH服务(Windows连接)

在日常的开发和管理工作中&#xff0c;通过SSH&#xff08;Secure Shell&#xff09;连接到远程服务器是一个非常常见的需求。如果你在尝试通过SSH连接到你的Ubuntu系统时遇到了问题&#xff0c;可能是因为SSH服务未安装或未正确配置。本文将介绍如何在Ubuntu上安装并启动SSH服…...

docker build时的网络问题

docker build时无法yum安装包&#xff0c;因为无法访问外网&#xff0c;无法ping通外网。 解决办法&#xff1a; systemctl stop NetworkManager.service firewall-cmd --permanent --zonetrusted --change-interfacedocker0 systemctl start NetworkManager.service systemct…...

Vue的安全性:防范XSS攻击与安全最佳实践

引言 随着Web应用的普及,前端安全问题日益受到重视。Vue作为当下流行的前端框架,其安全性也成为开发者关注的焦点。跨站脚本攻击(XSS)是常见的Web安全漏洞之一,本文将讨论如何在使用Vue时防范XSS攻击,并分享其他Vue中的安全最佳实践。 什么是XSS攻击? XSS攻击是一种将…...

ARM架构(一)—— ARMV8V9基础概念

目录 1.ARMCore的时间线2.ARM术语小结2.1 A64和arrch642.2ARM架构现在的5个系列2.3 微架构2.4 PE2.5 Banked2.6 ARM文档术语2.7 IMPLEMENTATION DEFINFD 和 DEPRECATED2.8 EL1t和EL1h 3 ARMv7的软件架构4 安全状态切换模型4.1 Secure state和Non-secure state介绍 5 Interproce…...

如何使用Python进行数据分析

Python是一种广泛应用于数据科学和机器学习领域的编程语言。本文将介绍如何使用Python进行数据分析&#xff0c;包括Python在数据分析中的应用场景、常用库和工具&#xff0c;以及实际案例分析。 一、Python在数据分析中的应用场景 数据清洗&#xff1a;处理缺失值、异常值&a…...

Python学习笔记40:游戏篇之外星人入侵(一)

前言 入门知识已经学完&#xff0c;常用标准库也了解了,pygame入门知识也学了&#xff0c;那么开始尝试小游戏的开发。 当然这个小游戏属于比较简单的小游戏&#xff0c;复杂的游戏需要长时间的编写累计开发经验&#xff0c;同时也需要一定的时间才能编写出来。现在的话还是嫩…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

FFmpeg avformat_open_input函数分析

函数内部的总体流程如下&#xff1a; avformat_open_input 精简后的代码如下&#xff1a; int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...