Python/OpenCVでLinuxの画面をキャプチャしてみる
OpenCVでWebカメラなんかの映像をキャプチャしようとすると、大抵の環境ではバックエンドとしてffmpegを使うようになっています。 このバックエンドは、コンパイル時のオプションで変えることが出来て、GStreamerを使うようにも出来るようです。
GStreamerを使うと、画面のキャプチャとかも出来るようで…ということは、リアルタイムにウィンドウの映像を取得して加工が出来る!? OBSごっことか出来てとても楽しそう。
というわけで、やってみました。
必要なものをコンパイル
Gentooを使っているとこのあたりは簡単。
gstreamer
ってUSEフラグを付けてコンパイルするだけで終わります。
Gentooでない場合はリポジトリからソースを落してきてよしなにコンパイルしてください。
$ sudo emerge USE=gstreamer opencv
テストでやるイメージで一行で書いていますが、実際は設定ファイルに書いておいたほうが良い気がします。
ついでに、ximagesrcというGStreamerのプラグインもインストールしておきます。 このプラグインがウィンドウのキャプチャを担当することになります。
$ sudo emerge gst-plugins-ximagesrc
対象のウィンドウIDを確認する
ウィンドウをキャプチャする前に、キャプチャしたいウィンドウのIDを確認します。
これにはxwininfo
ってコマンドが便利です。
$ xwininfo | grep 'Window id'
xwininfo: Window id: 0x220008b "python-opencv-screen-capture.mdx + (~) - VIM"
コマンドを実行すると固まるので、その状態でキャプチャしたいウィンドウをクリックすると上記のような出力を得られます。
この場合、0x220008b
の部分が後に使うウィンドウIDになります。
ウィンドウではなく画面をまるごとキャプチャしたい場合は、ウィンドウIDとして0
を使ってください。
省略すると勝手に0になるっぽいけれど、もし明示したいときには。
GStreamerだけで動作確認をしてみる
ximagesrcがちゃんとインストール出来ているかを確認するために、まずはGStreamer単体で動作確認をしてみます。
さきほど確認したウィンドウIDを使って、以下のようなコマンドを実行します。
$ WINDOW_ID='0x220008b' # さっき確認したID
$ gst-launch-1.0 ximagesrc xid=$WINDOW_ID ! videoconvert ! autovideosink
上手くいけば、新しくウィンドウが開いてキャプチャされた映像が表示されます。
右がコマンドを実行しているコンソールで、左がキャプチャされたウィンドウです。 分かりやすいように、スクリーン全体をキャプチャさせています。
補足: BadMatch (invalid parameter attributes)
エラーについて
キャプチャを実行しているときに元のウィンドウをリサイズしてしまうと、以下のようなエラーが出て動作が停止してしまいます。
X Error of failed request: BadMatch (invalid parameter attributes)
Major opcode of failed request: 130 (MIT-SHM)
Minor opcode of failed request: 4 (X_ShmGetImage)
Serial number of failed request: 40
Current serial number in output stream: 40
筆者はタイル型のウィンドウマネージャを使っているので、起動した瞬間にリサイズされてクラッシュして焦りました。 色々試してはみたのですが、回避の方法を見つけられませんでした。 とりあえずキャプチャ中はリサイズしない運用で。相性が悪い…。
Pythonからウィンドウをキャプチャする
PythonのOpenCVからGStreamerを扱うには以下のようにします。
コンパイル時にバックエンドを指定してあるので、cv2.VideoCapture
にgst-launch-1.0
に渡したようなオプションを直接渡すだけ。
import cv2
WINDOW_ID = '0x220008b' # さっき確認したID
video = cv2.VideoCapture(f'ximagesrc xid={WINDOW_ID} ! videoconvert ! appsink')
while cv2.waitKey(1) != 27:
ok, img = video.read()
if not ok:
break
cv2.imshow('test', img)
もちろん、xidに0を渡す(もしくはxidオプションを省略する)とスクリーン全体をキャプチャ出来ます。
ちなみに、フレームレートはウィンドウの更新に合わせて可変のようです。
画面が更新されない限り、read
メソッドでずっとブロックされ続けます。
追記
実験している中で、画像が変にズレるというか傾くというか、横幅を正しく検知出来ていないっぽい症状が出ることがありました。 以下の画像のような感じになっちゃう。
画像サイズが正しくないことが原因のようなので、videoscale
を挟んでサイズを明示してやることで解決出来ます。
video = cv2.VideoCapture(f'ximagesrc xid={WINDOW_ID} ! videoconvert ! videoscale ! video/x-raw,width={WIDTH},height={HEIGHT} ! appsink')
これで、以下のように綺麗に表示されるようになるはずです。
加工したりして楽しむ
試しに画面の色を反転させる(img = 255 - img
する)遊びをしてみました。
スクリーン全体を写してあげれば白黒を繰り返すように。
たのしい! …たのしいか?
参考: