[C++] opencv를 사용하는 방법(image를 동영상, 캠 이미지를 동영상으로 작성)


Development note/C , C++ , MFC  2020. 4. 27. 20:00

안녕하세요. 명월입니다.


이 글은 C++에서 opencv를 사용하는 방법(image를 동영상, 캠 이미지를 동영상으로 작성)에 대한 글입니다.


opencv는 이미지를 동영상으로 변환시켜 주는 라이브러리입니다.

동영상은 이미지를 연속적으로 보여주는 것입니다. 즉 24fps의 동영상이라고 한다면 1초에 24장의 이미지를 보여주므로 해서 움직이는 영상처럼 보이게 하는 것입니다.


먼저 이 opencv를 사용하기 위해서 라이브러리를 설치합니다.

링크 - https://opencv.org/

릴리즈 탭으로 이동합니다.

저는 Visual studio로 Window 환경에서 사용할 것이기 때문에 Window 버전을 다운로드 받습니다.


그리면 exe파일이 다운로드 됩니다.

실행을 압축이 풀리는데 적당한 곳에 압축을 풉니다. 저는 d:\opencv에 압축을 풀었습니다.

압축을 풀게 되면 source 폴더와 build 폴더가 나옵니다.

source는 opencv 라이브러리를 직접 수정해서 사용할 때 사용하는 것이고, 저는 빌드된 것만 사용할 것이기 때문에 source는 필요없습니다.


먼저 opencv는 x32비트가 없습니다.. 64용 뿐입니다.

그래서 디버그나 릴리즈를 x64비트로 설정하고 시작해야 합니다.

이제 다시 Visual studio에서 c++링커와 library 설정을 합니다.

먼저 해더 include입니다.

이번엔 library 추가입니다.

라이브러리가 opencv_world430d.lib가 있고 opencv_world430.lib 있는데 디버그용과 릴리즈용이 있습니다. 저는 디버그 용으로 설정했습니다.


그리고 opencv를 사용하기 위해서 dll 파일도 필요합니다. (정적 라이브러리만 사용할 꺼면 lib만 사용하지 dll 파일도 필요하네요..)

해당 프로젝트의 debug 폴더로 복사를 합니다.

여기까지 설정했으면 opencv를 실행해 보겠습니다.

먼저 예제는 제가 예전에 screenshot를 만든 소스가 있습니다.

그걸 이용해서 동영상을 만들어 보겠습니다.

#include <stdio.h>
#include <iostream>
#include <crtdbg.h>
#include <Windows.h>
#include <fstream>
#include "opencv2/opencv.hpp"

#if _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
// 흑백 모니터가 아닌 이상 요즘엔 보통 bit count가 24
#define BIT_COUNT 24;
using namespace std;
// opencv namespace
using namespace cv;
// 스크린 샷
Mat screenshot()
{
  // 스크린 크기를 저장하기 위한 변수
  RECT desktop;
  // 데스크 탑의 핸들
  const HWND hDesktop = GetDesktopWindow();
  // 핸들로 부터 크기를 받는다.
  GetWindowRect(hDesktop, &desktop);
  // 너비
  int width = desktop.right;
  // 높이
  int height = desktop.bottom;
  // 스크린 핸들러 취득
  HDC hScreen = GetDC(NULL);
  // 핸들러 생성
  HDC hDC = CreateCompatibleDC(hScreen);
  // 핸들러에 bitmap 핸들러 생성
  HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, width, height);
  // 핸들러 -> bitmap 핸들러 선택
  HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
  // hDC는 출력될 핸들러.
  // x, y, width, height -> 이건 위 hDC에 표시될 위치와 크기입니다.
  // 스크린 핸들러의 좌표 x, y위치부터
  // SRCCOPY - 원본 복사
  BOOL bRet = BitBlt(hDC, 0, 0, width, height, hScreen, 0, 0, SRCCOPY);

  BITMAPINFOHEADER bi;
  bi.biSize = sizeof(BITMAPINFOHEADER);
  bi.biWidth = width;
  bi.biHeight = -height;
  bi.biPlanes = 1;
  bi.biBitCount = BIT_COUNT;
  bi.biCompression = BI_RGB;
  bi.biSizeImage = 0;
  bi.biXPelsPerMeter = 0;
  bi.biYPelsPerMeter = 0;
  bi.biClrUsed = 0;
  bi.biClrImportant = 0;
  // opencv에서 사용되는 이미지 데이터
  Mat mat;
  // 생성
  mat.create(height, width, CV_8UC3);
  // 길이 맞추고
  StretchBlt(hDC, 0, 0, width, height, hScreen, 0, 0, width, height, SRCCOPY);
  // opencv에 bitmap 데이터를 넣는다.
  GetDIBits(hDC, hBitmap, 0, height, mat.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
  //핸들러 -> 리소스 돌리기
  SelectObject(hDC, old_obj);
  // 핸들러 삭제 (리소스 해제)
  DeleteDC(hDC);
  // 리소스 해제
  ReleaseDC(NULL, hScreen);
  // bitmap 리소스 해제
  DeleteObject(hBitmap);
  return mat;
}
// 실행 함수
int main()
{
  RECT desktop;
  // 데스크 탑의 핸들
  const HWND hDesktop = GetDesktopWindow();
  // 핸들로 부터 크기를 받는다.
  GetWindowRect(hDesktop, &desktop);
  // 너비
  int width = desktop.right;
  // 높이
  int height = desktop.bottom;
  // frame 속도
  int fps = 24;
  // 출력 포멧
  int fourcc = VideoWriter::fourcc('m', 'p', 'e', 'g');
  // 출력
  VideoWriter outputVideo("d:\\work\\test.mpg", fourcc, fps, Size(width, height), true);
  // 출력 Writer 오픈 확인
  if (!outputVideo.isOpened())
  {
    cout << " The file open is fault." << endl;
    return -1;
  }
  // 10초간 녹화를 한다.
  for (int i = 0; i < fps * 10; i++)
  {
    // 스크린 샷 데이터를 가져온다.
    Mat src = screenshot();
    // 파일로 전송
    outputVideo << src;
    // 이게 키를 대기하는 함수인데.. 쓰레드의 sleep의 역할도 한다.
    //if (waitKey(delay) == 27)
    if (waitKey(1) == 27)
    {
      break;
    }
  }

  _CrtDumpMemoryLeaks();
  return 0;
}

실행을 하면 조금 빠르다는 느낌이 있습니다. 이게 outputVideo의 fps를 /2로 맞추면 정상적으로 나오는데.. 이게 왜 이러는지 저도 잘 모르겠네요.

살펴보니 DC로부터 스크린샷 데이터, Mat를 취득하는게 굉장히 느리네요..이 부분 성능 향상을 해야겠습니다.


스샷은 이런데 캠으로 하면 정상적으로 출력이 됩니다.

#include <stdio.h>
#include <iostream>
#include "opencv2/opencv.hpp"
#if _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
using namespace std;
// opencv namespace
using namespace cv;
// 실행 함수
int main()
{
  // VideoCapture는 캠으로부터 오는 이미지를 취득하는 함수
  VideoCapture capture(0);
  // capture가 열려 있는지 확인
  if (!capture.isOpened())
  {
    // 에러시 콘솔 출력
    cout << "Can not open capture!!" << endl;
    return 0;
  }
  // 캠의 사이즈를 구한다.
  Size size = Size((int)capture.get(CAP_PROP_FRAME_WIDTH), (int)capture.get(CAP_PROP_FRAME_HEIGHT));
  // 1초당 24프레임
  int fps = 24;
  // 딜레이 초 계산
  int delay = 1000 / fps;
  // opevcv 이미지 변수
  Mat frame;
  // 캠의 촬영의 확인을 위한 opencv의 window창
  namedWindow("frame", WINDOW_AUTOSIZE);
  // 출력 포멧
  int fourcc = VideoWriter::fourcc('m', 'p', 'e', 'g');
  // 출력
  VideoWriter outputVideo("c:\\work\\test.mpg", fourcc, fps, size, true);
  // 출력 Writer 오픈 확인
  if (!outputVideo.isOpened())
  {
    // 에러시 콘솔 출력
    cout << "The file open is fault." << endl;
    return -1;
  }
  // 10초간 녹화를 한다.
  for (int i = 0; i < fps * 10; i++) 
  {
    // 캠으로 온 이미지를 Mat에 저장
    capture >> frame;
    // Mat 데이터가 비어 있으면 중지한다.
    if (frame.empty())
    {
      break;
    }
    // file로 출력한다.
    outputVideo << frame;
    // window에 표시한다.
    imshow("frame", frame);
    // 이게 키를 대기하는 함수인데.. 쓰레드의 sleep의 역할도 한다.
    if (waitKey(delay) == 27) 
    {
      break;
    }
  }
}

캠으로 온 데이터가 파일이 동영상 파일로 작성된 것이 확인이 됩니다.

opencv에서 저는 단순히 캠영상을 동영상으로 만드는 작업만 했는데 여기에 여러가지 이미지 삽입도 가능합니다. 즉, cam으로 촬영되는 동영상에 특정 이모티콘이나 이미지를 삽입할 수 있다는 뜻입니다.


그리고 opencv는 이미지를 단순히 동영상나 캠 영상을 동영상으로 만드는 라이브러리라 소리가 나오지 않습니다.

소리는 따로 DirectX나 winmm 라이브러리로 취득을 해서 ffmpeg으로 동영상을 합성해야 하는 처리가 필요합니다.

이건 따로 글을 작성해서 소개하겠습니다.


여기까지 C++에서 opencv를 사용하는 방법(image를 동영상, 캠 이미지를 동영상으로 작성)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.