web-dev-qa-db-ja.com

OpenCVでVideoCaptureからn番目ごとのフレームを読み取る

ビデオからフレームを段階的に読み取ることは可能ですか(たとえば、ビデオストリームの5フレームごとに読み取りたい)。現在、これを回避策として実行していますが、あまり効率的ではありません。

bool bSuccess
int FramesSkipped = 5;
 for (int a = 0;  < FramesSkipped; a++)
      bSuccess = cap.read(NextFrameBGR);

必要なフレームを取得するために5つのフレームをループする必要がないように、何か提案はありますか?

18
user3079474

できることは多くなく、OpenCVの欠点だけではありません。ご存知のように、現代のビデオコーデックは、一般に複雑な獣です。より高い圧縮率を得るために、フレームのエンコーディングは、多くの場合、以前のフレームに依存し、時には連続するフレームにも依存します。

そのため、ほとんどの場合、必要がない場合でも、目的のフレームの前にフレームをデコードする必要があります。

ビデオファイルを具体的にエンコードするには、かなり重要なトリックがあります。そのため、N番目のフレームごとに取得する方が安価ですが、一般的なケースでは実現できません。

つまり、OpenCVが提供するシーク機能を試すことができます( OpenCV Seek Function/Rewind を参照)。状況によっては、より高速に動作する場合と動作しない場合があります。しかし、個人的には、私はそれに賭けません。

20
Sergei Nosov

次のように、私はPython 3で単純なカウンターを使用してキャプチャをそのカウンターのフレームに設定することで成功しました:

import cv2

cap = cv2.VideoCapture('XYZ.avi')
count = 0

while cap.isOpened():
    ret, frame = cap.read()

    if ret:
        cv2.imwrite('frame{:d}.jpg'.format(count), frame)
        count += 30 # i.e. at 30 fps, this advances one second
        cap.set(1, count)
    else:
        cap.release()
        break

私はwithステートメントを使用してこれをもう少しPythonicにする方法を見つけようとしましたが、CV2ライブラリがそのために更新されたとは思いません。

10
benJephunneh

これが私が提案するものです:

     CvCapture* capture = cvCaptureFromFile("input_video_path");
     int loop = 0;
     IplImage* frame = NULL;
     Mat matframe;                                                                   
     char fname[20];                                                                 
     do {
        frame = cvQueryFrame(capture);  
        matframe = cv::cvarrToMat(frame);
        cvNamedWindow("video_frame", CV_WINDOW_AUTOSIZE);                                 
        cvShowImage("video_frame", frame);
        sprintf(fname, "frame%d.jpg", loop);
        cv::imwrite(fname, matframe);//save each frame locally
        loop++;
        cvWaitKey(100);
     } while( frame != NULL );

すべてのフレームをローカルに保存したので、必要なn番目のフレームをすばやく読み取ることができます。
[〜#〜] catuion [〜#〜]:私が持っていた12秒のサンプル動画は、200以上の画像で構成されていました。これは多くのスペースを消費します。

シンプルでありながら効果的な最適化は、使用しているアプローチまたは@sergieによって提案されたアプローチを使用してn番目のフレームを読み取ることです。この後、インデックスを付けて画像を保存できるため、後で同じインデックスでクエリを実行すると、現在のようにフレームをスキップする必要がなく、保存された画像が返されます。このようにして、照会していないフレームの保存に無駄になっていたスペースと、不要なフレームの読み取りと保存にかかる時間を節約できます。

1
Deepthought

OpenCVで同じ目標を達成したとき、フレームレートやフレームの総数に関係なく、ビデオの1秒あたりに必要な「キーフレーム」の数を調整しました。したがって、これにより、ターゲットのKPSを指定すると、N番目のキーが取得されます。

# python3.6 code using OpenCV 3.4.2

import cv2
KPS = 5 # Target Keyframes Per Second
VIDEO_PATH = "path/to/video/folder" # Change this
IMAGE_PATH = "path/to/image/folder" # ...and this 
EXTENSION = ".png"

cap = cv2.VideoCapture(VIDEO_PATH)
    # Set frames-per-second for capture
    fps = round(cap.get(cv2.CAP_PROP_FPS))
    hop = round(fps / KPS)
    curr_frame = 0
    while(True):
        ret, frame = cap.read()
        if not ret: break
        if curr_frame % hop == 0:
            print('Creating... {0}'.format(name,))
            name = IMAGE_PATH + "_" + str(curr_frame) + EXTENSION
            cv2.imwrite(name, frame)
        curr_frame += 1
    cap.release()

すべてのフレームをロールスルーしているが、Nとしてhopを使用してN番目のフレームのみを書き込むことに注意してください。

1
azatar

次のフレームに移動するには、grab関数を使用する必要があります。そして、必要なフレームのデコードにはretrieveのみを使用してください。

bool bSuccess
int FramesSkipped = 5;
for (int a = 0;  < FramesSkipped; a++)
      bSuccess = cap.grab();
bSuccess = cap.retrieve(NextFrameBGR);
1
vuamitom

同じ問題が発生しました。私がしたことはこれだけでした:

import cv2

vs = cv2.VideoCapture("<path of your video>.mp4")

print("Showing frames...")
c=1
while True:

    grabbed, frame = vs.read()
    if c%5==0:
        cv2.imshow('Frame',frame)
        cv2.waitKey(1)
    c+=1

vs.release()

お役に立てれば。

1
Ishan Shah

私は this repoを使用します!

主なアイデアは:

main.py

from camera import VideoCam
SKIPFRAME = 8
url = 0
v1 = VideoCam(url)
v1.check_camera(v1.cap)
ct = 0
while True:
    ct += 1
    try:
        ret = v1.cap.grab()
        if ct % SKIPFRAME == 0:  # skip some frames
            ret, frame = v1.get_frame()
            if not ret:
                v1.restart_capture(v1.cap)
                v1.check_camera(v1.cap)
                continue
            # frame HERE
            v1.show_frame(frame, 'frame')
    except KeyboardInterrupt:
        v1.close_cam()
        exit(0)

camera.py

import cv2
import logging


class VideoCam():
    def __init__(self, url=0):
        self.url = url
        self.cap = cv2.VideoCapture(self.url)
        self.get_frame()
        self.get_frame_read()
        logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)

    def check_camera(self, cap):
        logging.info('Camera {} status: {}'.format(self.url, cap.isOpened()))

    def show_frame(self, frame, name_fr='NAME'):
        cv2.imshow(name_fr, frame)
        # cv2.imshow(name_fr, cv2.resize(frame, (0, 0), fx=0.4, fy=0.4))
        cv2.waitKey(1)


    def get_frame(self):
        return self.cap.retrieve()

    def get_frame_read(self):
        return self.cap.read()

    def close_cam(self):
        self.cap.release()
        cv2.destroyAllWindows()

    def restart_capture(self, cap):
        cap.release()
        self.cap = cv2.VideoCapture(self.url)

ishan Shahのコードに基づいて、誰かが5番目ごとのフレームをキャプチャしてjpgとして保存する必要がある場合:

import cv2

vid = cv2.VideoCapture('C:/python_works/creatives_gardenscapes/0c52b83ed1dec617092aaf83278f12ad.mp4')

if not os.path.exists('images'):
    os.makedirs('images')

index = 0
while(True):
    ret, frame = vid.read()
    if not ret: 
        break
    name = 'C:/python_works/creatives_gardenscapes/frames/0c52b83ed1dec617092aaf83278f12ad' + str(index) + '.jpg'
    if index%50==0:
        cv2.imwrite(name, frame)
    index += 1
0
andrew-zmeul

エンコーディングスキームは一般に非常に複雑であるため、ランダムフレームを抽出することはできません。たとえば、MPEG-4では、2つのフレーム間の差異を含む情報のみが保存されるため、明らかに前のフレームが必要です。

0
Tessaracter