从非连续视频帧创建全景图

Create Panorama from Non-Sequential Video Frames

有个similar question (not that detailed and no exact solution). I want to create a single panorama image from video frames. And for that, I need to get minimum non-sequential video frames at first. A demo video file is uploaded here.

我需要什么

一种机制,不仅可以产生非连续的视频帧,而且可以用来创建全景图像。下面给出了一个示例。正如我们所见,要创建全景图像,所有输入样本必须彼此包含最小重叠区域,否则无法完成。

所以,如果我有以下视频帧的顺序

A, A, A, B, B, B, B, C, C, A, A, C, C, C, B, B, B ...

要创建全景图像,我需要获得如下信息 - 减少连续帧(或相邻帧)但重叠最少。

     [overlap]  [overlap]  [overlap] [overlap]  [overlap]
 A,    A,B,       B,C,       C,A,       A,C,      C,B,  ...

我尝试过并卡住的东西

提供了一个演示视频片段 above。要获得非序列视频帧,我主要依靠 ffmpeg 软件。

试用 1

ffmpeg -i check.mp4 -vf mpdecimate,setpts=N/FRAME_RATE/TB -map 0:v out.mp4

之后,在 out.mp4 上,我使用 opencv

对视频帧进行切片
import cv2, os 
from pathlib import Path

vframe_dir = Path("vid_frames/")
vframe_dir.mkdir(parents=True, exist_ok=True)

vidcap = cv2.VideoCapture('out.mp4')
success,image = vidcap.read()
count = 0

while success:
    cv2.imwrite(f"{vframe_dir}/frame%d.jpg" % count, image)     
    success,image = vidcap.read()
    count += 1

接下来,我水平旋转了这些保存的图像(因为我的视频是垂直视图)。

vframe_dir = Path("out/")
vframe_dir.mkdir(parents=True, exist_ok=True)

vframe_dir_rot = Path("vframe_dir_rot/")
vframe_dir_rot.mkdir(parents=True, exist_ok=True)

for i, each_img in tqdm(enumerate(os.listdir(vframe_dir))):
    image = cv2.imread(f"{vframe_dir}/{each_img}")[:, :, ::-1] # Read (with BGRtoRGB)
    
    image = cv2.rotate(image,cv2.cv2.ROTATE_180)
    image = cv2.rotate(image,cv2.ROTATE_90_CLOCKWISE)

    cv2.imwrite(f"{vframe_dir_rot}/{each_img}", image[:, :, ::-1]) # Save (with RGBtoBGR)

此方法(使用 ffmpeg)的输出没问题,但 不适合 创建全景图像。因为它没有在结果中顺序给出一些重叠的帧。因此无法生成全景

小径 2 -

ffmpeg -i check.mp4 -vf decimate=cycle=2,setpts=N/FRAME_RATE/TB -map 0:v out.mp4

根本没用。

小径 3

ffmpeg -i check.mp4 -ss 0 -qscale 0 -f image2 -r 1 out/images%5d.png

也不走运。但是,我发现最后一个 ffmpeg 命令已经很接近了,但还不够。与其他人相比,这给了我少量的非重复帧(好)但坏的是仍然 do not need 帧,我有点手动选择一些想要的帧,然后是 opecv 拼接算法作品。因此,在选择一些帧并旋转之后(如前所述):

stitcher = cv2.Stitcher.create()
status, pano = stitcher.stitch(images) # images: manually picked video frames -_- 

更新

经过一些尝试,我有点采用非编程解决方案。但很想看到 一种高效的程序化方法

在给定的演示视频中,我使用了 Adobe 个产品(premiere prophotoshop)来完成此任务,video instruction。但问题是,我首先通过 premier 拍摄了所有视频帧(没有掉落到任何帧,这将进一步计算成本)并使用 photoshop 来拼接它们(根据 youtube 视频说明).对于这些编辑器工具来说,它太重了,看起来也不是更好的方法,但到目前为止,输出比任何东西都好。虽然我只从 1200+ 中拍摄了很少(400+ 帧)视频帧。


这里有一些重大挑战。原版视频虽然有一些条件,但也太严重了。与给定的演示视频剪辑不同:

  • 这不是直截了当的,即相机抖动
  • 照明条件,即导致同一地点的不同视觉效果
  • 相机闪烁或条纹

此场景未包含在给定的演示视频中。这为从此类视频中创建全景图像带来了额外且严峻的挑战。即使使用非编程方式(使用 adobe 工具)我也无法使它变得更好。


但是,就目前而言,我只想从没有上述条件的给定演示视频中获取全景图像。但我很想知道对此有何评论或建议。

尝试使用以下命令调整 fps。

ffmpeg -i check.mp4 -vf fps=0.2 images%03d.bmp

我抽取视频的方法几乎是做拼接程序会做的事情来尝试将两个帧拼接在一起。我寻找匹配的特征点,只有当匹配点的数量低于我认为可接受的水平时,我才会保存帧。

为了拼接,我只使用了 OpenCV 的内置拼接器。如果你想避免使用 OpenCV 的解决方案,我可以重做没有它的代码(尽管我无法复制 opencv 所做的所有漂亮的清理步骤)。老实说,decimate 程序已经完成了通用缝合的大部分工作。

我从这里获取视频:https://www.videezy.com/nature/48905-rain-forest-pan-shot

这是全景图(在截断 = 50 时减少到 7 帧)

虽然这是一个非常理想的情况,因此对于像您描述的那样更困难的视频,此策略可能会失败。如果您可以 post 该视频,那么我们可以在实际用例中测试此解决方案,并在需要时进行修改。

我喜欢这个节目。这些平移镜头很酷。这是此视频中的另一个:https://www.videezy.com/abstract/41671-pan-of-bryce-canyon-in-utah-4k

(在截止 = 50 时减少到 4 帧)

https://www.videezy.com/nature/11664-panning-shot-of-red-peaks-and-green-valleys-in-4k

(在截断 = 150 时减少到 4 帧)

大屠杀

import cv2
import numpy as np
import os
import shutil

# rescale the images
def rescale(img):
    scale = 0.5;
    h,w = img.shape[:2];
    h = int(h*scale);
    w = int(w*scale);
    return cv2.resize(img, (w,h));

# delete and create directory
folder = "frames/";
if os.path.isdir(folder):
    shutil.rmtree(folder);
os.mkdir(folder);

# open vidcap
cap = cv2.VideoCapture("PNG_7501.mp4"); # your video here
counter = 0;

# make an orb feature detector and a brute force matcher
orb = cv2.ORB_create();
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False);

# store the first frame
_, last = cap.read();
last = rescale(last);
cv2.imwrite(folder + str(counter).zfill(5) + ".png", last);

# get the first frame's stuff
kp1, des1 = orb.detectAndCompute(last, None);

# cutoff, the minimum number of keypoints
cutoff = 50; 
# Note: this should be tailored to your video, this is high here since a lot of this video looks like

# count number of frames
prev = None;
while True:
    # get frame
    ret, frame = cap.read();
    if not ret:
        break;

    # resize
    frame = rescale(frame);

    # count keypoints
    kp2, des2 = orb.detectAndCompute(frame, None);

    # match
    matches = bf.knnMatch(des1, des2, k=2);

    # lowe's ratio
    good = []
    for m,n in matches:
        if m.distance < 0.5*n.distance:
            good.append(m);

    # check against cutoff
    print(len(good));
    if len(good) < cutoff:
        # swap and save
        counter += 1;
        last = frame;
        kp1 = kp2;
        des1 = des2;
        cv2.imwrite(folder + str(counter).zfill(5) + ".png", last);
        print("New Frame: " + str(counter));

    # show
    cv2.imshow("Frame", frame);
    cv2.waitKey(1);
    prev = frame;

# also save last frame
counter += 1;
cv2.imwrite(folder + str(counter).zfill(5) + ".png", prev);

# check number of saved frames
print("Counter: " + str(counter));

订书机

import cv2
import numpy as np
import os

# target folder
folder = "frames/";

# load images
filenames = os.listdir(folder);
images = [];
for file in filenames:
    # get image
    img = cv2.imread(folder + file);

    # save
    images.append(img);

# use built in stitcher
stitcher = cv2.createStitcher();
(status, stitched) = stitcher.stitch(images);
cv2.imshow("Stitched", stitched);
cv2.waitKey(0);