とある科学の備忘録

とある科学の備忘録

CやPythonのプログラミング、Arduino等を使った電子工作をメインに書いています。また、木製CNCやドローンの自作製作記も更新中です。たまに機械学習とかもやってます。

【C++/OpenCV】スクリーンキャプチャした映像を表示 or 動画ファイルとして保存

タイトルの通りです。
C++を使ってスクリーンキャプチャを行います。

ちなみに、Python版は↓です。
shizenkarasuzon.hatenablog.com
 
 

ではまず、ただ単にモニター映像をリアルタイムで表示するプログラムから(録画機能は無し)

プログラム1(キャプチャ映像表示)

# include <opencv2/opencv.hpp>
# include <opencv2/highgui.hpp>
# include <opencv2/videoio.hpp>
#include <Windows.h>

int main(int argc, char * const argv[]){
  // モニターサイズ取得
  HWND desktop = GetDesktopWindow();
  RECT rect;
  GetWindowRect(desktop, &rect);


  // DIBの情報を設定する
  BITMAPINFO bmpInfo;
  bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmpInfo.bmiHeader.biWidth = rect.right;
  bmpInfo.bmiHeader.biHeight = -rect.bottom;
  bmpInfo.bmiHeader.biPlanes = 1;
  bmpInfo.bmiHeader.biBitCount = 24; //cv::Matの画像をアルファチャンネル有りにする場合はは32;
  bmpInfo.bmiHeader.biCompression = BI_RGB;


  LPDWORD lpPixel;
  HDC hDC = GetDC(desktop);
  HBITMAP hBitmap = CreateDIBSection(hDC, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
  HDC hMemDC = CreateCompatibleDC(hDC);
  SelectObject(hMemDC, hBitmap);

  cv::Mat monitor_img;
  monitor_img.create(rect.bottom, rect.right, CV_8UC3); //RGBのみ。アルファちゃんねるを加えるにはCV_8UN4
  
  // 画像表示用のWindowを作成(ウィンドウの大きさを640x480に固定する)
  cv::namedWindow("Screenshot", cv::WINDOW_NORMAL);
  cv::resizeWindow("Screenshot", 640, 480);

  while (true){
    //hDCの画像(スクリーンショット)をhMemDCにコピーする
    BitBlt(hMemDC, 0, 0, rect.right, rect.bottom, hDC, 0, 0, SRCCOPY);
    // hMemDCの内容をcv::Matの画像(monitor_img)にコピー
    GetDIBits(hMemDC, hBitmap, 0, rect.bottom, monitor_img.data, (BITMAPINFO *)&bmpInfo, DIB_RGB_COLORS);  //copy from hwindowCompatibleDC to hbwindow

    cv::imshow("Screenshot", monitor_img); // スクリーンショットの表示    
    if (cv::waitKey(1) == 27) break; //Escキーで終了
  }

  //メモリ開放
  ReleaseDC(desktop, hDC);
  DeleteDC(hMemDC);
  DeleteObject(hBitmap);

  return 0;
}

 

実行結果1

プログラムを実行すると、下の画像の様にスクリーンの映像がリアルタイムで表示されます
f:id:pythonjacascript:20200625013755j:plain
終了するにはEscキーを押してください。


プログラム2(キャプチャした映像をmp4で保存)

上のプログラム1を少し改良して、スクリーンキャプチャした映像をmp4形式で録画してみようと思います。

# include <opencv2/opencv.hpp>
# include <opencv2/highgui.hpp>
# include <opencv2/videoio.hpp>
#include <Windows.h>

#include <string>
#include <vector>
#include <time.h>
#include <conio.h>

void main() {
  /* デスクトップのサイズ */
  HWND desktop = GetDesktopWindow();
  RECT rect;
  GetWindowRect(desktop, &rect);

  int width = rect.right;
  int height = rect.bottom;

  // DIBの情報を設定する
  BITMAPINFO bmpInfo;
  bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmpInfo.bmiHeader.biWidth = width;
  bmpInfo.bmiHeader.biHeight = -height;
  bmpInfo.bmiHeader.biPlanes = 1;
  bmpInfo.bmiHeader.biBitCount = 24; //cv::Matの画像をアルファチャンネル有りにする場合はは32;
  bmpInfo.bmiHeader.biCompression = BI_RGB;


  LPDWORD lpPixel;
  HDC hDC = GetDC(desktop);
  HBITMAP hBitmap = CreateDIBSection(hDC, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
  HDC hMemDC = CreateCompatibleDC(hDC);
  SelectObject(hMemDC, hBitmap);

  cv::Mat monitor_img;
  monitor_img.create(height, width, CV_8UC3); //RGBのみ。アルファちゃんねるを加えるにはCV_8UN4


  //スクリーンショットの映像一覧
  std::vector<cv::Mat> screen_shot_list;

  std::cout << "Start Recording !!" << std::endl;
  clock_t start = clock();
  cv::Mat cloned_img;

  while (_kbhit() == 0) { //何か入力があるまで録画し続ける
    //hDCの画像(スクリーンショット)をhMemDCにコピーする
    BitBlt(hMemDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY);
    // hMemDCの内容をcv::Matの画像(monitor_img)にコピー
    GetDIBits(hMemDC, hBitmap, 0, height, monitor_img.data, (BITMAPINFO *)&bmpInfo, DIB_RGB_COLORS);  //copy from hwindowCompatibleDC to hbwindow
    cloned_img = monitor_img.clone(); // cv::Matはポインターのようなやつなので、cloneして実体を作らないと正しくpush_backできない
    screen_shot_list.push_back(cloned_img);
  }


  //cv::String fname("SC.mp4");
  clock_t end = clock();
  double duration = static_cast<double>(end - start) / CLOCKS_PER_SEC * 1000.0;
  int fps = int(float(screen_shot_list.size() * 1000)/ duration);
  std::cout << "-------------------------------------" << std::endl;
  std::cout << "duration[ms]  = " << duration << std::endl;
  std::cout << "frame  = " << screen_shot_list.size() << std::endl;
  std::cout << "FPS  = "          << fps      << std::endl;
  //std::cout << "saving as \"" << "SC.mp4" << " \"" << std::endl;
  std::cout << "width  = " << width  << std::endl;
  std::cout << "height = " << height << std::endl;
  std::cout << "-------------------------------------\n";


  //http://shibafu3.hatenablog.com/entry/2016/11/13/151118
  int codec;
//  codec = cv::VideoWriter::fourcc('x', '2', '6', '4');
//  codec = cv::VideoWriter::fourcc('m', 'p', 'e', 'g');
  codec = cv::VideoWriter::fourcc('m', 'p', '4', 'v');

  cv::VideoWriter writer = cv::VideoWriter("SC.mp4", codec, fps, cv::Size(width, height));
    
  for (int i = 0; i < screen_shot_list.size(); i++) {
    // 1フレーム書き込む
    // writer.write(screen_shot_list[i])でも可
    writer << screen_shot_list[i]; 
  }
    
  writer.release();
  
  std::cout << "wrote video successfully !!\n";
  std::cout << "Prease press some key...\n";
  std::string s;
  std::cin >> s;

  //メモリ開放
  ReleaseDC(desktop, hDC);
  DeleteDC(hMemDC);
  DeleteObject(hBitmap);

  return;
}



実行結果2

上のプログラムを実行すると、

Start Recording !!

と表示されます。この状態から録画スタートです。

そして、録画を停止するには何かキーを押してください。すると、

-------------------------------------
duration[ms]  = 5622
frame  = 167
FPS  = 29
width  = 1536
height = 864
-------------------------------------

のような表示が出て、その後SC.mp4という名前でキャプチャ映像が書きだされるはずです。