在OpenCV中读写视频与读写图像非常相似。视频就是一系列通常被称为帧的图像。所以,你需要做的就是循环播放视频序列中的所有帧,然后一次处理一帧。在这篇文章中,我们将演示如何从一个文件、一个图像序列和一个网络摄像头读取、显示和写入视频。我们还将研究流程中可能发生的一些错误,并帮助理解如何解决这些错误。
让我们先看一下读取视频文件的代码示例。它本质上包含了从磁盘读取视频并显示它的功能。随着您的深入,我们将详细讨论这个实现中使用的函数。
; Python
import cv2
vid_capture = cv2.VideoCapture('Resources/Cars.mp4')
if (vid_capture.isOpened() == False):
print("Error opening the video file")
else:
fps = vid_capture.get(5)
print('Frames per second : ', fps,'FPS')
frame_count = vid_capture.get(7)
print('Frame count : ', frame_count)
while(vid_capture.isOpened()):
ret, frame = vid_capture.read()
if ret == True:
cv2.imshow('Frame',frame)
key = cv2.waitKey(20)
if key == ord('q'):
break
else:
break
vid_capture.release()
cv2.destroyAllWindows()
C++
#include
#include
using namespace std;
using namespace cv;
int main()
{
VideoCapture vid_capture("Resources/Cars.mp4");
if (!vid_capture.isOpened())
{
cout << "Error opening video stream or file" << endl;
}
else
{
int fps = vid_capture.get(5);
cout << "Frames per second :" << fps;
int frame_count = vid_capture.get(7);
cout << " Frame count :" << frame_count;
}
while (vid_capture.isOpened())
{
Mat frame;
bool isSuccess = vid_capture.read(frame);
if(isSuccess == true)
{
imshow("Frame", frame);
}
if (isSuccess == false)
{
cout << "Video camera is disconnected" << endl;
break;
}
int key = waitKey(20);
if (key == 'q')
{
cout << "q key is pressed by the user. Stopping the video" << endl;
break;
}
}
vid_capture.release();
destroyAllWindows();
return 0;
}
以下是我们将在这篇博文中讨论的OpenCV视频I/O的主要功能:
- cv2.videoccapture—创建一个视频捕获对象,它将帮助流或显示视频。
- cv2.VideoWriter -将输出的视频保存到一个目录中。
- 此外,我们还讨论了其他需要的函数,如cv2.imshow(), cv2.waitKey()和get()方法,用于读取视频元数据,如帧高度,宽度,帧数等。
在这个例子中,你将读取上面的视频(' Cars.mp4 ')并显示它
现在让我们从它开始。
首先,我们导入 OpenCV
库。请注意,对于c++,你通常会使用 cv::function()
,但因为我们选择使用cv命名空间(使用cv命名空间),所以我们可以直接访问OpenCV函数,而不需要在函数名前添加cv::。
C++
#include
#include
using namespace std;
using namespace cv;
从文件中读取视频
面的代码块使用 VideoCapture()
类来创建 VideoCapture
对象,然后我们将使用它来读取视频文件。使用这个类的语法如下所示:
VideoCapture(path, apiPreference)
第一个参数是视频文件的文件名/路径。第二个参数是一个可选参数,指示API首选项。与这个可选参数相关的一些选项将在下面进一步讨论。要了解更多关于apiPreference,请访问官方文档链接videocaptureapi。
Python
vid_capture = cv2.VideoCapture('Resources/Cars.mp4')
C++
Create a video capture object, in this case we are reading the video from a file
VideoCapture vid_capture("Resources/Cars.mp4");
现在我们有了一个视频捕获对象,我们可以使用 isopen()
方法来确认视频文件已成功打开。 isopen()
方法返回一个布尔值,指示视频流是否有效。否则,您将得到一条错误消息。错误消息可以包含许多内容。其中之一就是整个视频都损坏了,或者一些帧损坏了。假设视频文件已成功打开,我们可以使用 get()
方法检索与视频流相关联的重要元数据。请注意,此方法不适用于网络摄像机。get()方法从这里记录的选项枚举列表中获取一个参数。在下面的例子中,我们提供了数值5和7,它们对应于帧速率(CAP_PROP_FPS)和帧计数(CAP_PROP_FRAME_COUNT)。可以提供数值或名称。
Python
if (vid_capture.isOpened() == False):
print("Error opening the video file")
else:
fps = int(vid_capture.get(5))
print("Frame Rate : ",fps,"frames per second")
frame_count = vid_capture.get(7)
print("Frame count : ", frame_count)
C++
if (!vid_capture.isOpened())
{
cout << "Error opening video stream or file" << endl;
}
else
{
int fps = vid_capture.get(5):
cout << "Frames per second :" << fps;
frame_count = vid_capture.get(7);
cout << "Frame count :" << frame_count;
}
在获取与视频文件相关联的所需元数据之后,我们现在就可以从文件中读取每个图像帧了。这是通过创建一个循环并使用 vid_capture.read()
方法从视频流中每次读取一帧来实现的。
vid_capture.read()
方法返回一个元组,其中第一个元素是布尔值,下一个元素是实际的视频帧。当第一个元素为 True
时,它表示视频流包含一个要读取的帧。
如果有要读取的帧,那么可以使用 imshow()
在窗口中显示当前帧,否则退出循环。注意,您还使用 waitKey()
函数在视频帧之间暂停 20
毫秒。调用 waitKey()
函数可以监视键盘以获取用户输入。例如,在本例中,如果用户按下 ' q '
键,则退出循环。
Python
while(vid_capture.isOpened()):
ret, frame = vid_capture.read()
if ret == True:
cv2.imshow('Frame',frame)
k = cv2.waitKey(20)
if k == 113:
break
else:
break
C++
while (vid_capture.isOpened())
{
Mat frame;
bool isSuccess = vid_capture.read(frame);
if(isSuccess == true)
{
imshow("Frame", frame);
}
if (isSuccess == false)
{
cout << "Video camera is disconnected" << endl;
break;
}
int key = waitKey(20);
if (key == 'q')
{
cout << "q key is pressed by the user. Stopping the video" << endl;
break;
}
}
一旦视频流被完全处理或用户提前退出循环,你释放视频捕获对象 (vid_capture)
并关闭窗口,使用以下代码:
Python
vid_capture.release()
cv2.destroyAllWindows()
C++
vid_capture.release();
destroyAllWindows();
读取图像序列
处理来自图像序列的图像帧与处理来自视频流的图像帧非常相似。只需指定正在读取的图像文件。
在下面的例子中,
- 继续使用视频捕获对象
- 但是,您只需指定一个图像序列,而不是指定一个视频文件
- 使用下面所示的符号(Cars%04d.jpg),其中%04d表示四位数序列命名约定(例如Cars0001.jpg、Cars0002.jpg、Cars0003.jpg等)。
- 如果您指定了" Race_Cars_%02d.jpg ",那么您将查找的文件的形式:
(Race_Cars_01.jpg, Race_Cars_02.jpg, Race_Cars_03.jpg, etc...).
第一个示例中描述的所有其他代码都是相同的。
Python
vid_capture = cv2.VideoCapture('Resources/Image_sequence/Cars%04d.jpg')
C++
VideoCapture vid_capture("Resources/Image_sequence/Cars%04d.jpg");
从摄像头读取视频
从网络摄像机读取视频流也非常类似于上面讨论的例子。那怎么可能?这都要感谢 OpenCV
中视频捕获类的灵活性,它有几个方便接受不同输入参数的重载函数。不需要为视频文件或图像序列指定源位置,只需给出一个视频捕获设备索引,如下所示。
- 如果你的系统有一个内置的网络摄像头,那么摄像头的设备索引将是
“0”
。 - 如果你有多个摄像头连接到你的系统,那么与每个额外摄像头相关联的设备索引将递增(例如
1、2、3
等)。
Python
vid_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)
C++
VideoCapture vid_capture(0);
您可能想知道标志 CAP_DSHOW
。这是一个可选参数,因此不是必需的。 CAP_DSHOW
只是另一个视频捕获 API
首选项,它是 directshow
通过视频输入的简称。
写视频
现在让我们来看看如何写视频。就像视频读取一样,我们可以编写来自任何源(视频文件、图像序列或网络摄像头)的视频。写入视频文件:
- 使用get()方法检索图像帧的高度和宽度。
- 初始化一个视频捕获对象(如前面所讨论的),使用前面描述的任何源代码将视频流读入内存。
- 创建一个视频写入器对象。
- 使用视频写入器对象将视频流保存到磁盘。
继续我们的运行示例,让我们首先使用 get()
方法来获取视频帧的宽度和高度。
Python
frame_width = int(vid_capture.get(3))
frame_height = int(vid_capture.get(4))
frame_size = (frame_width,frame_height)
fps = 20
C++
Int frame_width = static_cast<int>(vid_capture.get(3));
int frame_height = static_cast<int>(vid_capture.get(4));
Size frame_size(frame_width, frame_height);
int fps = 20;
如前所述,videoccapture()类的get()方法需要:
- 来自枚举列表的单个参数,它允许您检索与视频帧关联的各种元数据。
可用的元数据非常广泛,可以在这里找到。
- 在本例中,您通过指定
3 (CAP_PROP_FRAME_WIDTH)和4 (CAP_PROP_FRAME_HEIGHT)
来检索帧的宽度和高度。当将视频文件写入磁盘时,您将在下面进一步使用这些尺寸。
为了编写视频文件,首先需要从 VideoWriter()
类中创建一个视频编写器对象,如下面的代码所示。
以下是 VideoWriter()
的语法:
VideoWriter(filename, apiPreference, fourcc, fps, frameSize[, isColor]
VideoWriter()类接受以下参数:
- filename:输出视频文件的路径名
- apiPreference: API后端标识符
- fourcc: 4个字符的编解码器代码,用于压缩帧(fourcc)
- fps:创建的视频流的帧率
- frame_size:视频帧的大小
- isColor:如果不是0,编码器将期望并编码彩色帧。否则它将与灰度帧工作(该标志目前仅在Windows上支持)。
下面的代码创建了视频写入器对象,输出自 VideoWriter()
类。一个特殊的方便函数用于检索四个字符的编解码器,需要作为视频编写器对象cv2的第二个参数。
-
VideoWriter_fourcc('M', 'J', 'P', 'G') in Python.
-
VideoWriter::fourcc('M', 'J', 'P', 'G') in C++.
视频编解码器指定视频流如何压缩。它将未压缩的视频转换为压缩格式,反之亦然。创建 AVI
或 MP4
格式,使用以下的 fourcc
规格:
AVI: cv2.VideoWriter_fourcc('M','J','P','G')
MP4: cv2.VideoWriter_fourcc(*'XVID')
接下来的两个输入参数指定帧速率(FPS)和帧大小(宽度、高度)。
Python
output = cv2.VideoWriter('Resources/output_video_from_file.avi', cv2.VideoWriter_fourcc('M','J','P','G'), 20, frame_size)
C++
VideoWriter output("Resources/output.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'),frames_per_second, frame_size);
在您已经创建了一个视频写入器对象,使用它将视频文件写入磁盘,一次一帧,如下面的代码所示。在这里,您正在以每秒20帧的速度将一个AVI视频文件写入磁盘。请注意我们是如何将前面的例子简化为循环的。
Python
while(vid_capture.isOpened()):
ret, frame = vid_capture.read()
if ret == True:
output.write(frame)
else:
print('Stream disconnected')
break
C++
while (vid_capture.isOpened())
{
// Initialize frame matrix
Mat frame;
// Initialize a boolean to check if frames are there or not
bool isSuccess = vid_capture.read(frame);
// If frames are not there, close it
if (isSuccess == false)
{
cout << "Stream disconnected" << endl;
break;
}
// If frames are present
if(isSuccess == true)
{
//display frames
output.write(frame);
// display frames
imshow("Frame", frame);
// wait for 20 ms between successive frames and break
// the loop if key q is pressed
int key = waitKey(20);
if (key == ‘q’)
{
cout << "Key q key is pressed by the user.
Stopping the video" << endl;
break;
}
}
}
最后,在下面的代码中,释放视频捕获和视频编写对象。
Python
vid_capture.release()
output.release()
C++
vid_capture.release();
output.release();
Original: https://blog.csdn.net/weixin_38346042/article/details/122580840
Author: @BangBang
Title: Opencv基础 (二):使用OpenCV读取和写入视频
相关阅读1
Title: 虚拟数字人很忙
最近,虚拟数字人有点忙,在直播,品牌营销,企业服务上都看到虚拟数字人身影。虚拟数字人跟元宇宙有什么关系,我们一起来说说。
一、虚拟数字人概念
1、定义
"虚拟数字人"一词最早源于 1989 年美国国立医学图书馆发起的"可视人计划"(Visible Human Project, YHP)。其指存在于非物理世界中,由计算机图形学、图形渲染、动作捕捉、深度学习、语音合成等计算机手段创造及使用,并具有多重人类特征(外貌特征、人类表演能力、人类交互能力等)的综合产物。
- 外貌特征:具有特定的相貌、性别和性格等人物特征
- 人类表演能力:拥有人的行为,具有用语言、面部表情和肢体动作表达的能力
- 人类交互能力:拥有人的思想,具有识别外界环境、并能与人交流互动的能力
"人"是其中的核心的因素,高度拟人化为用户带来的亲切感、关怀感与沉浸感是多数消费者的核心使用动力。能否提供足够自然逼真的相处体验,将成为虚拟数字人在各个场景中取代真人,完成语音交互方式升级的重要标准。
2、发展历程
从最早的手工绘制到现在的 CG(Computer Graphics,电脑绘图)、人工智能合成,虚拟数字人大致经历了萌芽、探索、初级和成长四个阶段。
二、虚拟数字人相关技术
1、虚拟数字人通用系统框架
虚拟数字人系统一般情况下由人物形象、语音生成、动画生成、音视频合成显示、交互等 5 个模块构成。
- 人物形象:根据人物图形资源的维度,可分为 2D 和 3D 两大类,从外形上又可分为卡通、拟人、写实、超写实等风格
- 语音生成模块和动画生成模块:可分别基于文本生成对应的人物语音以及与之相匹配的人物动画
- 音视频合成显示模块:将语音和动画合成视频,再显示给用户
- 交互模块:使数字人具备交互功能,即通过语音语义识别等智能技术识别用户的意图,并根据用户当前意图决定数字人后续的语音和动作,驱动人物开启下一轮交互
交互模块根据其有无,可将数字人分为交互型数字人和非交互型数字人。
- 非交互型数字人:系统依据目标文本生成对应的人物语音及动画,并合成音视频呈现给用户
- 交互型数字人:根据驱动方式的不同可分为智能驱动型和真人驱动型
- 智能驱动型数字人:该人物模型是预先通过AI技术训练得到可通过文本驱动生成语音和对应动画,业内将此模型称为TTSA(Text To Speech & Animation)人物模型
- 真人驱动型数字人:真人根据视频监控系统传来的用户视频,与用户实时语音,同时通过动作捕捉采集系统将真人的表情、动作呈现在虚拟数字人形象上,从而与用户进行交互
(智能驱动型虚拟数字人运作流程)
2、三大核心技术
为了实现"拟人化",技术层面主要体现为以下三点:
- CG建模/图像迁移技术:影响外观呈现。体现为虚拟数字人外观的拟人程度
- NLP交互技术:影响交互体验。以对话能力为核心,该技术继续在虚拟数字人中发挥核心作用,可以视作为虚拟数字人的脑
- CV等深度学习模型:影响驱动效果。受数据量、计算框架、关键特征点等因素深刻影响。能否呈现自然的面部表情变动、肢体变动等,在极大程度上取决于语音驱动的深度模型效果
三、虚拟数字人产业应用
1、行业现状
按照产业应用,可以将虚拟数字人划分为两类,服务型虚拟数字人和身份型虚拟数字人。"量子位虚拟数字人产业报告"预测,在2030年,我国虚拟数字人整体市场规模将达到2700亿。其中,得益于虚拟IP的巨大潜力,以及虚拟第二分身的起步,身份型虚拟数字人将占据主导地位,约1750亿,并逐步成为Metaverse中的重要一环。服务型虚拟数字人则相对稳定发展,多模态AI助手仍有待进一步发展,多种对话式服务升级至虚拟数字人形态,总规模超过950亿。
2、产业划分
虚拟数字人的产业链从下到上分为基础层、平台层、应用层,下层赋能上层并不断合作形成了多元的商业模式。
- 基础层:为虚拟数字人提供基础软硬件支撑,硬件包括显示设备、光学器件、传感器、芯片等,基础软件包括建模软件、渲染引擎
- 平台层:包括软硬件系统、生产技术服务平台、AI 能力平台,为虚拟数字人的制作及开发提供技术能力
- 应用层:指虚拟数字人技术结合实际应用场景领域,切入各类,形成行业应用解决方案,赋能行业领域
3、虚拟IP应用Case
虚拟IP相对于真人IP,解决了MCN对特定IP长期稳定持有的问题,以偶像/网红为核心场景,在直播、代言等领域均有所发展。由于我国的短视频和直播业态正在迅速发展,面对高频、碎片且实时的IP运营需求,虚拟IP更能够适应这一趋势。代言领域,欧莱雅(M姐)、花西子(花西子)已开始有所尝试,通过虚拟IP打造完全符合品牌调性的虚拟代言人。
四、总结
技术是进入行业的核心门槛,需要进入玩家在机器视觉、语音交互和自然语言处理方面都具有深厚的技术积累,并将三者进行有机结合。当前虚拟数字人向自我管理的演化,认知智能、灵活性、个性化、情感化都是虚拟数字人需要进一步提升的技术方面。
尽管技术壁垒增加了商业化成本,但虚拟数字人给元宇宙打开了更大的想象空间。你是否也想拥有一个虚拟数字人呢?
参考文献:
《2020 年虚拟数字人发展白皮书》中国人工智能产业发展联盟总体组,中关村数智人工智能产业联盟数字人工作委员会
《虚拟数字人深度产业报告》量子位
Original: https://www.cnblogs.com/muobver/p/16257591.html
Author: MuObver
Title: 虚拟数字人很忙
相关阅读2
Title: pd.DataFrame()函数解析
DataFrame是Python中Pandas库中的一种数据结构,它类似excel,是一种二维表。==
DataFrame的单元格可以存放数值、字符串等,这和excel表很像,同时DataFrame可以设置列名columns与行名index。
1、创建DataFrame
1.1函数创建
pandas常与numpy库一起使用,所以通常会一起引用
其中第一个参数是存放在DataFrame里的数据,第二个参数 index
就是之前说的行名,第三个参数 columns
是之前说的列名。
其中后两个参数可以使用list输入,但是注意,这个list的长度要和DataFrame的大小匹配,不然会报错
当然,这两个参数是可选的,你可以选择不设置,而且这两个list是可以一样的。
1.2直接创建
1.3字典创建
使用head可以查看前几行的数据,默认的是前5行,不过也可以自己设置。
使用tail可以查看后几行的数据,默认也是5行,参数可以自己设置。
比如看前5行。
比如只看前2行。
比如看后5行。
比如只看后2行。
使用values可以查看DataFrame里的数据值,返回的是一个数组。
比如说查看某一列所有的数据值。
如果查看某一行所有的数据值。使用iloc查看数据值(但是好像只能根据行来查看?),iloc是根据数字索引(也就是行号)。可以看一下博客pandas.iloc()函数解析。
使用shape查看行列数,参数为0表示查看行数,参数为1表示查看列数。
使用冒号进行切片。
- 切片表示的是行切片
- 索引表示的是列索引
直接字母T,线性代数上线。
使用describe可以对数据根据列进行描述性统计。
如果有的列是非数值型的,那么就不会进行统计。
如果想对行进行描述性统计,转置后再进行descr
使用sum默认对每列求和,sum(1)为对每行求和。
数乘运算使用apply。
乘方运算跟matlab类似,直接使用两个*。
扩充列可以直接像字典一样,列名对应一个list,但是注意list的长度要跟index的长度一致。
还可以使用insert,使用这个方法可以指定把列插入到第几列,其他的列顺延。
使用join可以将两个DataFrame合并,但只根据行列名合并,并且以作用的那个DataFrame的为基准。
但是,join这个方法还有how这个参数可以设置,合并两个DataFrame的交集或并集。参数为'inner'表示交集,'outer'表示并集。
如果要合并多个Dataframe,可以用list把几个Dataframe装起来,然后使用concat转化为一个新的Dataframe。
参数:
- subset:指定是哪些列重复。
- keep:去重后留下第几行,{'first', 'last', False}, default 'first'},如果是False,则去除全部重复的行。
- inplace:是否作用于原来的df。
去除重复行,保留重复行中最后一行
去除'c'列中有重复的值所在的行
Original: https://www.cnblogs.com/andrew-address/p/13040035.html
Author: 蛮好不太坏
Title: pd.DataFrame()函数解析
相关阅读3
Title: 计算机视觉(三)图像拼接
文章目录
*
- 一、流程与概念
-
+ 1.1 流程
+ 1.2 映射与处理
- 二、算法
-
+ 2.1 全景拼接
+ 2.2 RANSAC算法
+ 2.3 APAP算法
+ 2.4 图像重合区处理
+ 2.5 multi-band blending算法
- 三、实现与运行
-
+ 3.1 代码实现
+ 3.2 结果
一、流程与概念
1.1 流程
要拼接多张图像,就一定要找到他们之间的映射关系,流程如下:
得到映射关系,就能进行拼接:
简而言之,拼接两张图像,就是找到他们的特征点,根据这些特征点:
; 1.2 映射与处理
找到两张图像的像素点对应关系,然后把第二张图像映射到在第一张图像的坐标系下,二者合成新的图像,若是有多张图,就重复这个步骤。
下面给出了不同映射需要的特征点数:
二、算法
2.1 全景拼接
将SIFT应用到图像拼接上,根据特征点匹配的方式,则利用这些匹配的点来估算单应矩阵使用RANSAC算法,也就是把其中一张通过个关联性和另一张匹配的方法。通过单应矩阵H,可以将原图像中任意像素点坐标转换为新坐标点,转换后的图像即为适合拼接的结果图像。
可以简单分为以下几步:
1.根据给定图像/集,实现特征匹配。
2.通过匹配特征计算图像之间的变换结构。
3.利用图像变换结构,实现图像映射。
4.针对叠加后的图像,采用APAP之类的算法,对齐特征点。(图像配准)
5.通过图割方法,自动选取拼接缝。
6.根据multi-band blending策略实现融合。
2.2 RANSAC算法
在找特征点时,不可避免的会出现噪声,因此要找到一种算法,让多张图像的特征点能够正确的进行映射。
用RANSAC 算法求解求解单应矩阵。
RANSAC[1] (随机抽样一致)是一种迭代算法,该算法从一组包含"外点(outlier)"的观测数据中估计数学模型的参数。"外点"指观测数据中的无效数据,通常为噪声或错误数据,比如图像匹配中的误匹配点和曲线拟合中的离群点。与"外点"相对应的是"内点(inlier)",即用来估计模型参数的有效数据。因此,RANSAC也是一种"外点"检测算法。此外,RANSAC算法是一种非确定算法,它只能在一定概率下产生可信的结果,当迭代次数增加时,准确的概率也会增加。
RANSAC算法是用来找到正确模型来拟合带有噪声数据的迭代方法。
基本思想:数据中包含正确的点和噪声点,合理的模型应该能够在描述正确数据点的同时摈弃噪声点。
流程如下:
; 2.3 APAP算法
在图像拼接融合的过程中,受客观因素的影响,拼接融合后的图像可能会存在"鬼影现象"以及图像间过度不连续等问题。下图就是图像拼接的一种"鬼影现象"。解决鬼影现象可以采用APAP算法。
算法流程:
1.提取两张图片的sift特征点
2.对两张图片的特征点进行匹配
3.匹配后,使用RANSAC算法进行特征点对的筛选,排除错误点。筛选后的特征点基本能够一一对应
4.使用DLT算法,将剩下的特征点对进行透视变换矩阵的估计
5.由于得到的透视变换矩阵是基于全局特征点对进行的,即一个刚性的单应性矩阵完成配准。为提高配准的精度,Apap将图像切割成无数多个小方块,对每个小方块进行单应性矩阵变换,非常依赖于特征点对。若图像高频信息较少,特征点对过少,配准将完全失效,并且对大尺度的图像进行配准,其效果也不是很好,一切都决定于特征点对的数量。
2.4 图像重合区处理
拼接两张图像,重叠部分的像素值取决于谁,是有说法的。
如图,用一种方式,在这部分区域划出一条路线,左边用A图,右边用B图
最大流
给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow)。
最小割
割是网络中定点的一个划分,它把网络中的所有顶点划分成两个顶点集合S和T,其中源点s∈S,汇点t∈T。记为CUT(S,T),满足条件的从S到T的最小割(Min cut)。
最大流的值等于最小割的容量。
给出福特福克森算法如下:
; 2.5 multi-band blending算法
在找完拼接缝后,由于图像噪声、光照、曝光度、模型匹配误差等因素,直接进行图像合成会在图像重叠区域的拼接处出现比较明显的边痕迹。这些边痕迹需要使用图像融合算法来消除。而multi-band blending是目前图像融和方面比较好的方法。
multi-band bleing策略采用Laplacian(拉普拉斯)金字塔,通过对相邻两层的高斯金字塔进行差分,将原图分解成不同尺度的子图,对每一个之图进行加权平均,得到每一层的融合结果,最后进行金字塔的反向重建,得到最终融合效果过程,融合之后可以得到较好的拼接效果。
三、实现与运行
3.1 代码实现
from pylab import *
from numpy import *
from PIL import Image
from PCV.geometry import homography, warp
from PCV.localdescriptors import sift
np.seterr(invalid='ignore')
"""
This is the panorama example from section 3.3.
"""
featname = ['E:/CV/img/img' + str(i) + '.sift' for i in range(4)]
imname = ['E:/CV/img/img' + str(i) + '.jpg' for i in range(4)]
l = {}
d = {}
for i in range(4):
sift.process_image(imname[i], featname[i])
l[i], d[i] = sift.read_features_from_file(featname[i])
matches = {}
for i in range(3):
matches[i] = sift.match(d[i + 1], d[i])
for i in range(3):
im1 = array(Image.open(imname[i]))
im2 = array(Image.open(imname[i + 1]))
figure()
sift.plot_matches(im2, im1, l[i + 1], l[i], matches[i], show_below=True)
def convert_points(j):
ndx = matches[j].nonzero()[0]
fp = homography.make_homog(l[j + 1][ndx, :2].T)
ndx2 = [int(matches[j][i]) for i in ndx]
tp = homography.make_homog(l[j][ndx2, :2].T)
fp = vstack([fp[1], fp[0], fp[2]])
tp = vstack([tp[1], tp[0], tp[2]])
return fp, tp
model = homography.RansacModel()
fp, tp = convert_points(1)
H_12 = homography.H_from_ransac(fp, tp, model)[0]
fp, tp = convert_points(0)
H_01 = homography.H_from_ransac(fp, tp, model)[0]
tp, fp = convert_points(2)
H_32 = homography.H_from_ransac(fp, tp, model)[0]
delta = 2000
im1 = array(Image.open(imname[1]), "uint8")
im2 = array(Image.open(imname[2]), "uint8")
im_12 = warp.panorama(H_12, im1, im2, delta, delta)
im1 = array(Image.open(imname[0]), "f")
im_02 = warp.panorama(dot(H_12, H_01), im1, im_12, delta, delta)
im1 = array(Image.open(imname[3]), "f")
im_32 = warp.panorama(H_32, im1, im_02, delta, delta)
figure()
imshow(array(im_32, "uint8"))
axis('off')
show()
3.2 结果
原图与运行结果如下:
Original: https://blog.csdn.net/qq_45749702/article/details/124136571
Author: 浅雨梦梨
Title: 计算机视觉(三)图像拼接