[Java] 영상에서 썸네일(Thumbnail) 추출하기

    영상에서 썸네일을 자동으로 추출하여, 썸네일을 만들거나 이미지 파일을 분석하는 방법을 활용할 수 있을 것이다. 필자는 썸네일의 용도보다는 주기적으로 영상에 이미지를 추출하여 사진을 분석하는 용도로 사용하기 위해서 구글링을 하게 되었다.


    다양한 방법이 있고, 라이브러리마다 성능의 차이는 있을 수 있다. 예를 들어 좀 더 복잡한 방식은 더 높은 성능을 자랑할 수 있다. 단순한 라이브러리는 상대적으로 낮은 성능(ex: 추출 속도)을 보여줄 수 있지만 이미지를 수집하는데 큰 지장이 없기에 이번에는 쉬운 방법의 썸네일을 추출하는 라이브러리를 소개하고자 한다.




    Maven 설정


    Java 기반이며 필자는 Maven 기반으로 프로젝트를 생성하였다. 라이브러리는 "jcodec"이라는 라이브러리를 활용하였는데 아래 2개의 Dependency를 추가하면 된다.


    <dependency>

    <groupId>org.jcodec</groupId>

    <artifactId>jcodec</artifactId>

    <version>0.2.3</version>

    </dependency>

    <dependency>

    <groupId>org.jcodec</groupId>

    <artifactId>jcodec-javase</artifactId>

    <version>0.2.3</version>

    </dependency>



    jcodec의 Support 정보


    jcodec은 다음과 같은 비디오와 오디오, 포맷등을 지원한다


    Video Codecs

    H.264 main profile decoder;

    H.264 baseline profile encoder;

    VP8 decoder (I frames only);

    VP8 encoder (I frames only);

    MPEG 1/2 decoder ( I/P/B frames, interlace );

    Apple ProRes decoder/encoder;

    JPEG decoder;

    PNG decoder/encoder.

    DivX/Xvid


    Audio Codecs

    SMPTE 302M decoder;

    AAC decoder (JAAD)

    RAW PCM.


    Formats

    MP4 ( MOV ) demuxer / muxer;

    MKV ( Matroska ) demuxer / muxer;

    MPEG PS demuxer;

    MPEG TS demuxer;

    WAV demuxer/muxer;

    MPEG Audio (MP3) demuxer;

    ADTS demuxer.

    DPX parser


    대부분의 동영상을 지원한다고 봐도 무방할 것 같다.



    Source 및 설명


    main.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import java.io.File;
     
    public class Main {
     
        public static void main(String[] args) throws Exception {
            Mp4Test mp4Test = new Mp4Test();
            File source = new File(파일경로);        
            
            long startTime = System.currentTimeMillis();
            mp4Test.getThumbnail(source);
            long endTime = System.currentTimeMillis();
            System.out.println((endTime-startTime) + "(ms)");
        }
    cs


    파일경로 부분은 절대경로를 입력한다. 예를 들어 C:/Test 폴더에 a.mp4 라는 비디오의 이미지를 캡쳐하고 싶으면, "C:/Test/a.mp4" 라고 입력을 한다.



    Mp4Test.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import java.io.File;
    import java.io.IOException;
     
    import org.jcodec.api.JCodecException;
     
    public class Mp4Test { 
        
        /**
        * 썸네일을 추출하는 메소드
        * 
        * @param source mp4 file.
        * @param thumbnail
        * @return
        * @throws IOException
        * @throws JCodecException
         * @throws InterruptedException 
        */
        public void getThumbnail(File source) throws Exception {
            double plusSize = 0.5;
            int threadSize = 8;
         
            VideoThread[] videoThread = new VideoThread[threadSize];
         
            for(int i = 0; i < videoThread.length; i++) {
                videoThread[i] = new VideoThread(source, threadSize, i, plusSize);
                videoThread[i].start();
            }
            
            boolean runFlag = true;
            while(runFlag) {
                Thread.sleep(1000);
                
                runFlag = false;
                for(int i = 0; i < threadSize; i++) {
                    if(videoThread[i].isAlive())
                        runFlag = true;
                }            
            }
        }
    }
    cs

    필자의 설정은 0.5초마다 이미지를 추출하는 방식이다. 그리고 속도를 높이기 위해서 스레스(Thread)를 사용하였는데 정확한 시간(총 걸린시간)을 측정하기 위해서 스레드가 모두 종료가 되야지 해당 메소드가 종료되게 만들었다. 



    VideoThread.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    import java.awt.image.BufferedImage;
    import java.io.File;
     
    import javax.imageio.ImageIO;
     
    import org.jcodec.api.FrameGrab;
    import org.jcodec.common.io.NIOUtils;
    import org.jcodec.common.model.Picture;
    import org.jcodec.scale.AWTUtil;
     
    public class VideoThread extends Thread {
        private int threadNo;
        private int threadSize;
        private double plusSize;
        private File source;
        
        public VideoThread(File source, int threadSize, int threadNo, double plusSize) {
            this.source = source;
            this.threadSize = threadSize;
            this.threadNo = threadNo;
            this.plusSize = plusSize;
        }
     
        public void run() {
            FrameGrab grab;
            
            try {
                grab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(source));
                
                for(int m = 0; m < 120; m++) {
                    if(m % threadSize == threadNo) {
                        double startSec = m * plusSize;
                        System.out.println(threadNo + " " + startSec);
                        
                        int frameCount = 1;
                        grab.seekToSecondPrecise(startSec);
                        
                        for (int i=0; i < frameCount; i++) {
                            Picture picture = grab.getNativeFrame();
                             
                            //for JDK (jcodec-javase)
                            BufferedImage bufferedImage = AWTUtil.toBufferedImage(picture);
                            ImageIO.write(bufferedImage, "png"
                                    new File("E:/Project/steel/image/frame" + m + ".png"));   
                        }
                    }
                }    
            } catch (Exception e1) {
                e1.printStackTrace();
            }            
        }
    }
    cs


    실질적으로 라이브러리를 호출하여 작동을 하는 클래스가 바로 VideoThread.java이다. 이미지를 추출하면, png 포맷으로 파일을 저장하게 되어 있는 구조이다.



    실행결과



    필자 컴퓨터 Core가 8개 이고, Thread를 8로 설정하였기 때문에 CPU를 Full로 사용하는 것을 볼 수 있다. 1분정도 되는 영상을 데탑 8코어 Cpu로 120장을 추출하는데 


    37815(ms)


    약 37초 정도가 걸린 것을 확인할 수 있다. 


    Jcodec에 관련된 설명은 github에서 확인하여 필요한 부분을 직접 코딩하는 것을 권유드린다.



    참고자료


    댓글

    Designed by JB FACTORY