raspi2pngが超便利!ラズパイのCUI環境でフレームバッファのスクリーンショットを撮る方法 +BGRA→RGB変換プログラム

Raspbian Liteを入れてCUI環境で動かしているRaspberry Pi Zero W。

軽快にさくさく動いて良いのですが、デスクトップ環境(X)がないのでスクリーンショットを撮るには一筋縄ではいきません。

なかなかマニアックな、CUI環境でフレームバッファのデータからスクリーンショット(PNG画像)を作成する方法を紹介します。

 

スクリーンショットを撮る(超簡単な方法)

フレームバッファをrawデータとして保存する方法は簡単に見つかるのですが、そこから一般的な画像形式(pngとか)にする方法が見つからず、色々探す事小1時間。

完璧な「raspi2png」を発見しました!

githubからcloneしてきます。
※初めての方はgitコマンドをインストール。

$ sudo apt-get install git

$ mkdir ~/git
$ cd ~/git
$ git clone https://github.com/AndrewFromMelbourne/raspi2png.git

一応バイナリが入っているのですが、自分環境で作り直します。
コンパイルにはlibpngが必要です。

$ sudo apt-get install libpng12-dev
$ cd raspi2png
$ make

完了したら、キャプチャしてみましょう。

$ ./raspi2png -p ss.png

これで、あっさりとスクリーンショットが撮れました!

どこからでも使えるようにパスが通る場所に設置しておきます。

$ sudo install -m 0755 ./raspi2png /usr/local/bin/raspi2png

 

途中で行き詰まった方法

ググるとたくさん出てくるフレームバッファを直接ファイルに保存する方法を試してみました。

フレームバッファをraw画像ファイルに書き出します。

$ cat /dev/fb0 > ss.raw

出来上がったファイルはRGBにAlphaを加えた32bitのデータになります。(width x height x 4byte)

rawtoppmというコマンドで、raw画像をPPM形式に変換します。画面の縦横サイズが必要なので予めfbsetで調べておきます。

$ fbset
 mode "1920x1200"
 geometry 1920 1200 1920 1200 32
 timings 0 0 0 0 0 0 0
 rgba 8/16,8/8,8/0,8/24
 endmode

コマンドが入っていない場合は、インストールします。

$ sudo apt-get install netpbm

PPM形式に変換します。

$ rawtoppm 1920 1200 ss.raw > ss.ppm

そして、ppmをconvertコマンド(ImageMagick)でpngに変換します。convertコマンドが使えるようにimageMagickを入れておきます(最初だけ)。

$ sudo apt-get install imagemagick

変換します。

$ convert -depth 8 -size 1920x1200 ss.ppm ss.png

できあがったファイルをPCで開いて見ると、、、

ぐちゃぐちゃ!

rawtoppm24bitデータ(RGB)にしか対応していないのが原因と思われます。

最初にできたrawデータ(ss.raw)のサイズをls -l で見てみると、
9216000
となっています。

画面の横幅 x 画面の縦幅 x 4byteを計算すると、
1920x1200x4(byte)=9216000
ちょうどピッタリ。

ということで、4byte(BGRA)を3byte(RGB)に変換してからrawtoppm→convertとすれば良さそうです。

 

上手くいった方法 →【フレームバッファのBGRAデータをRGBに変換する自作プログラム】

データをppm形式に変換するところでつまずいたので、フレームバッファのrawデータであるBGRA(各8bitRGBカラーデータにαを加えた32bitのデータ)からRGBを抜き出してくるプログラムを作りました。

変換プログラム(C言語編)

実行スピードを考慮して、C言語で作りました。

  • フレームバッファ(画面サイズ)を小さくしてデータ量を少なくしています(方法は最後に記載)。
  • 例外処理を入れ始めるととても長くなるので、全部決め打ちしています。
  • ファイル名やサイズを後から指定する場合は、引数として与えて参照するように修正すると良いです。
$ vi argb2rgb.c
#include <stdio.h>
#define BUFSIZE1 (1280*720*4)
#define BUFSIZE2 (1280*720*3)
#define _FIN_ "ss.raw"
#define _FOUT_ "ss.dat"

int main (void){
	FILE *fin, *fout;
	unsigned char ibuf[BUFSIZE1], obuf[BUFSIZE2];
	unsigned int i, size, num;
	
	fin=fopen(_FIN_,"rb");
	fout=fopen(_FOUT_, "wb");

	size = fread(ibuf, sizeof(unsigned char), BUFSIZE1, fin);
	
	num = (int)(size/4);
	printf("size: %d, num: %d\n", size, num);

	for (i=0; i<num; i++){
		obuf[3*i+0] = ibuf[4*i+2];
		obuf[3*i+1] = ibuf[4*i+1];
		obuf[3*i+2] = ibuf[4*i+0];
	}

	fwrite(obuf, sizeof(unsigned char), BUFSIZE2, fout);

	fclose(fin);
	fclose(fout);
	
	return 0;
}

※最初に定義しているバッファサイズはfbsetで確認した値を使います。

$ fbset
mode "1280x720"
 geometry 1280 720 1280 720 32
 timings 0 0 0 0 0 0 0
 rgba 8/16,8/8,8/0,8/24
endmode

入力用には×4、出力用には×3しています。
※ファイルサイズを取得して動的にやれば良いのですが、手抜き、もとい、要点だけを分かり易くするために最小限にしています。

コンパイルします。

$ gcc -o argb2rgb argb2rgb.c

あとは、同じフォルダにss.rawデータを置いて実行すればOKです。
(詳細は変換手順にて)

 

変換プログラム(Python編)

Python3でも変換するコードを書いてみました。

#!/usr/bin/env python3
# coding: utf-8

import sys

with open(sys.argv[1], 'rb') as fin: # バイナリ・リードモードで開く
	data = fin.read() # データを一括読み込み
	num = int(len(data)/4) # BGRAがいくつあるか計算
	print('len: {}, num: {}'.format(len(data), num)) # 確認用

	with open(sys.argv[2], 'wb') as fout: # バイナリ・ライトモードで開く
		odata = bytearray() # 空っぽのbytearrayを作成
		for i in range(num): # numだけ回す。Aだけ抜いてRGBとして格納
			odata.append(data[4*i+2]) # R
			odata.append(data[4*i+1]) # G
			odata.append(data[4*i+0]) # B

		fout.write(odata) # ファイルに書き出して完了

C言語より恐ろしくシンプル。。。

実行権限を付けておきます。

$ chmod +x argb2rgb.py

 

変換手順

フレームバッファをrawデータとして保存します。

$ cat /dev/fb0 > ss.raw

変換プログラムを実行します。

$ ./argb2rgb
 または、
$ ./argb2rgb.py ss.raw ss.dat

pythonの場合は引数に入力ファイルと出力ファイルを指定します。
※注)ラズパイzeroだとかなり時間がかかります。

出来上がったss.datをppm形式に変換します。

$ rawtoppm -rgb 1280 720 ss.dat > ss.ppm

png形式に変換します。

$ convert -depth 8 -size 1280x720 ss.ppm ss.png

これでスクリーンショット画像の出来上がり!

 

C vs Python 変換時間の比較

当初Pythonでやっていたのですが、あまりにも変換に時間がかかったのでCで書き直しました。どれくらい違うかをラズパイZero Wで比較してみました。

まずはPythonで実行。

$ time ./argb2rgb.py ss.raw ss.dat
 len: 3686400, num: 921600

 real 0m39.166s
 user 0m39.020s
 sys 0m0.110s

次にコンパイル済みのCで実行。

$ time ./argb2rgb
 size: 3686400, num: 921600

 real 0m0.195s
 user 0m0.100s
 sys 0m0.080s

40秒 vs 0.2秒 ということで、200倍の差が付きました^^;

 

おまけ(フレームバッファのサイズを変更)

接続しているディスプレイによっては解像度が高すぎて、スクリーンショットが大きくなってしまいます(文字が小さい!)

config.txtを編集して適度なサイズに変更することで対応します。

$ cd /boot
$ sudo cp config.txt config.txt.org
$ vim config.txt

フレームバッファの縦横サイズのコメントアウトを消します。

# uncomment to force a console size. By default it will be display's size minus
 # overscan.
 framebuffer_width=1280
 framebuffer_height=720

保存して終了し、再起動後に無事に表示されたら完了です。

 

まとめ

ラズパイのCUI環境でスクリーンショットを撮る方法を紹介しました。
世の中には便利な物を公開してくださる方がおられ、ありがたい限りです^^

また、自作プログラムで変換する方法も開拓できました。
画像処理の初歩的な理解にも役立ちそうです。