비디오의 모든 프레임을 추출하는 법 (Extracting all frame from video)

안녕하세요 코더스하이!
이번에 개발중인 앱에서 핵심 기능인 비디오에서 프레임을 추출하는 부분에 문제가 있어 질문드립니다.
참고할만한 레퍼런스나 링크 무엇이든 답변해주시면 감사하겠습니다!

구현하고자 하는 기능

  • 설정된 fps 값을 바탕으로 카메라롤에서 선택한 비디오의 모든 프레임을 이미지 배열로 저장
  • 썸네일이 아닌 해상도 감소가 없는 이미지로 추출

문제점

  • 1분이 넘는 동영상을 아래 코드로 추출해도 총 16개의 프레임 밖에 나오지 않음
  • 비디오에 따라 같은 frame 이 여러번 추출되는 경우도 있었음

시도한 코드 1

     private func video2ImageGenerator(video_url url : URL, mediaType type : String){

        //https://stackoverflow.com/questions/42665271/swift-get-all-frames-from-video
        let videoURL : URL = url
        var generator : AVAssetImageGenerator!

        let asset : AVAsset = AVAsset(url: videoURL)
        let duration: Float64 = CMTimeGetSeconds(asset.duration)
        generator = AVAssetImageGenerator(asset: asset)
        generator.appliesPreferredTrackTransform = true

                  
        let tracks = asset.tracks(withMediaType: .video)
        let fps = ceil((tracks.first?.nominalFrameRate)!)
        let totalFrameNum = Int(Double(fps) * duration)

        print("duration: \(duration)")
        print("total frame: \(totalFrameNum)")
        print("fps: \(fps)")

        let timeScale = 100
        let convertedFps = 1
        let convertedTimeGap = Int32(timeScale/convertedFps)

        var index = 0

        while(Int32(index) * convertedTimeGap < Int32(duration * Double(timeScale))){
            let time:CMTime = CMTimeMakeWithSeconds(Float64(index), preferredTimescale: convertedTimeGap)
            print("time: \(time)")
            let image:CGImage
            do {
                try image = generator.copyCGImage(at: time, actualTime: nil)
            }catch {
                print("pass")
                return
            }
            imageArray.append(UIImage(cgImage: image))
            index = index + 1
        }

        print("image frame count : \(self.imageArray.count)")
    }

시도한 코드2

import UIKit

import AVFoundation
import AVKit
import Photos
import Combine

class Video2Image{
    let assetIG: AVAssetImageGenerator
    let fps: Int
    let running_time: Double
    let total_frame_num: Int
    
    init(
        video_url: URL
    ){
//        let video_url = Bundle.main.url(forResource: resource_name, withExtension: suffix_name)!
        let video_url = video_url
        let asset = AVAsset(url: video_url)
        self.assetIG = AVAssetImageGenerator(asset: asset)
        assetIG.appliesPreferredTrackTransform = true
        assetIG.apertureMode = AVAssetImageGenerator.ApertureMode.encodedPixels
        let tracks = asset.tracks(withMediaType: .video)
        self.fps = Int(ceil((tracks.first?.nominalFrameRate)!))
        self.running_time = Double(asset.duration.value) / 600.0
        self.total_frame_num = Int(Double(fps) * running_time)
    }
    
    func getSingleFrame(frame: Int) -> UIImage? {
        let timestamp = Double(frame) / Double(fps)
        let cm_time = CMTime(seconds: timestamp, preferredTimescale: 60)
        let image_ref: CGImage
        do {
            image_ref = try self.assetIG.copyCGImage(at: cm_time, actualTime: nil)
        } catch let error{
            print("Error: \(error)")
            return nil
        }
        return UIImage(cgImage: image_ref)
    }
    
}

참고 글

좋아요 1

안녕하세요, 리서치를 더 해야 하는데, 그럼 너무 늦어질 것 같아서 일단 아이디어만 먼저 공유합니다.

먼저, AVAssetImageGenerator 함수는 싱크러너스 한 함수라서 상황에 영향을 많이 받습니다. 일련의 이미지를 추출하려면 generateCGImagesAsynchronously(forTimes:completionHandler:) 를 사용하시기 바랍니다.

예전엔 QuickTime 라이브러리와 QTKit 프레임워크를 썼었는데, 이젠 모든 애플문서가 QTKit을 AVFoundation 으로 이전하는 걸 이야기하는 걸 보니, AVFoundation 안에서 해결이 가능하겠네요.

좋아요 1

리서치를 좀 더 해봐야겠는데요…

모든 프레임을 이미지 배열로 메모리에 올리려면

예를 들어서 압축없다고 가정하면

1024 * 768 픽셀짜리는 1024 * 768 * 8(bit color)로 약 786,432 바이트. 색상 8bit 를 1byte로 취급.

1분짜리 영상이면 초당 30프레임만 잡아도 60s * 30f = 1,800 장이 됩니다.

1,800 * 786,432 = 1,415,577,600 바이트라서 이론상 약 1.4기가 정도의 메모리가 필요합니다.

또한 비디오 포맷이 모든 프레임을 이미지 형태로 저장하는게 아니라, 일련의 압축 과정이 있다보니 사진과 같은 이미지를 얻을 수 있을지 모르겠네요. 좀 리서치가 필요합니다.

좋아요 2