TL;DR:mat画像をコンソール上に表示できることを確認しました。
- TUI(Terminal User Interface)の表示手段として、libsixelが利用できることを確認した。
- 比較的簡単な方法でコンソール上に画面表示できることを確認できた。
はじめに:ふと思ったのです、コンソールに画像出せたらちょっと幸せ、かもと。
- OpenCVで画像確認するときにわざわざ
window表示
とかメンドクサイ。 - gtk3とかインストールするのもメンドクサイ。
- だったら、matをそのままコンソール画面上に表示するにはどうしたらいいのか!?
コンソールに画像を出すにはどうするのか -> libsixelを使えばよい
コンソール上で画像を表示する方法としては、libsixelを用いるとよい、らしい。
それではこれで遊んでいく!なお、ライセンスはMITライセンス、と書かれている。
libsixel ですかね!
(え?出力結果をjpgで書き出してこのツール通せばいい、そんな野暮な話はやめてください、泣きます)
開発環境
ライブラリインストール
kmtr@ubuntu2104opencv:~/work$ sudo apt install libsixel*
この操作後に追加で 5,094 kB のディスク容量が消費されます。
取得:1 http://jp.archive.ubuntu.com/ubuntu hirsute/universe amd64 libsixel1 amd64 1.8.6-2 [94.6 kB]
取得:2 http://jp.archive.ubuntu.com/ubuntu hirsute/universe amd64 libsixel-bin amd64 1.8.6-2 [17.8 kB]
取得:3 http://jp.archive.ubuntu.com/ubuntu hirsute/universe amd64 libsixel-dev amd64 1.8.6-2 [125 kB]
取得:4 http://jp.archive.ubuntu.com/ubuntu hirsute/universe amd64 libsixel-examples all 1.8.6-2 [3,579 kB]
対応している端末のインストール(mlterm)
ubuntu 21.04で試したところ、mltermでは画面表示ができたので、これを使っていく!
表示可能? | |
---|---|
Gnome端末 | ✕ |
xterm(361) | ✕ |
mlterm(3.9.0) | ◯ |
kmtr@ubuntu2104opencv:~/work$ sudo apt install mlterm
以下のパッケージが新たにインストールされます:
libssh2-1 mlterm mlterm-common mlterm-tools ncurses-term
アップグレード: 0 個、新規インストール: 5 個、削除: 0 個、保留: 0 個。
1,866 kB のアーカイブを取得する必要があります。
この操作後に追加で 13.4 MB のディスク容量が消費されます。
続行しますか? [Y/n] Y
取得:1 http://jp.archive.ubuntu.com/ubuntu hirsute/universe amd64 libssh2-1 amd64 1.9.0-2 [87.7 kB]
取得:2 http://jp.archive.ubuntu.com/ubuntu hirsute/main amd64 ncurses-term all 6.2+20201114-2build1 [249 kB]
取得:3 http://jp.archive.ubuntu.com/ubuntu hirsute/universe amd64 mlterm-common amd64 3.9.0-1 [1,242 kB]
取得:4 http://jp.archive.ubuntu.com/ubuntu hirsute/universe amd64 mlterm amd64 3.9.0-1 [228 kB]
取得:5 http://jp.archive.ubuntu.com/ubuntu hirsute/universe amd64 mlterm-tools amd64 3.9.0-1 [59.4 kB]
1,866 kB を 1秒 で取得しました (2,133 kB/s)
ということで、img2sixelコマンドを使って、jpeg画像をコンソール表示はできた!
確かにこれでもいいんですけどね……
system()
関数使って、img2sixelで画像を表示させてもいいんですけどね、、、 やっぱりなんか残念な感じなので、プログラマらしく「不要な努力」かましたいです!
ライブラリのつなぎ方を確認する
どうやらこのあたりにファイルがインストールされたらしい。
お、pkg-configにも繋がっている、これは勝ちましたかね!!
kmtr@ubuntu2104opencv:~/work/sixel$ find /usr/ -name "libsixel*"
/usr/lib/x86_64-linux-gnu/pkgconfig/libsixel.pc
/usr/lib/x86_64-linux-gnu/libsixel.so
/usr/lib/x86_64-linux-gnu/libsixel.so.1
/usr/lib/x86_64-linux-gnu/libsixel.so.1.0.6
/usr/lib/x86_64-linux-gnu/libsixel.a
/usr/share/lintian/overrides/libsixel1
/usr/share/lintian/overrides/libsixel-dev
/usr/share/lintian/overrides/libsixel-bin
/usr/share/doc/libsixel-examples
/usr/share/doc/libsixel1
/usr/share/doc/libsixel-dev
/usr/share/doc/libsixel-bin
/usr/bin/libsixel-config
kmtr@ubuntu2104opencv:~/work/sixel$ pkg-config libsixel --cflags --libs
-I/usr/include/sixel -lsixel
kmtr@ubuntu2104opencv:~/work/sixel$ ls /usr/include/sixel/
sixel.h
サンプルコードを見ながら解析
初期化コードはここからですねー
sixel_output_t *output = NULL;
sixel_dither_t *dither = NULL;
status = sixel_output_new(&output, sixel_write, stdout, NULL);
if (SIXEL_FAILED(status))
goto end;
dither = sixel_dither_get(SIXEL_BUILTIN_G8);
sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_G8);
status = sixel_encode(c.buf, c.width, c.height, 0, dither, output);
if (SIXEL_FAILED(status))
goto end;
画面表示はここから。
status = sixel_encode(c.buf, c.width, c.height, 0, dither, output);
if (SIXEL_FAILED(status)) {
goto end;
}
動作結果
サンプルコード
先にライセンスの話を…
当該サンプルコードの一部はlibsixelのサンプルコードをベースとしております。こちらは、下記の通りCC0となっております、
I declared main.c is in Public Domain (CC0 - "No Rights Reserved"). This example is offered AS-IS, without any warranty.
実装
こんな感じになりました。 本当は、std::cout << src << std::endl;
で画像表示までやりたかったのですが、 めんどくさ 複雑になりすぎるので、とりあえずこれで完成とします!!
a.out : a.cpp
g++ a.cpp `pkg-config opencv5 libsixel --libs --cflags `
# include <iostream>
# include <opencv2/opencv.hpp>
# include <sixel.h>
struct canvas {
int width;
int height;
int skip;
unsigned char *buf;
};
static int sixel_write(char *data, int size, void *priv)
{
return fwrite(data, 1, size, (FILE *)priv);
}
static void show_to_sixel(cv::Mat src)
{
struct canvas c;
SIXELSTATUS status = SIXEL_FALSE;
sixel_output_t *output = NULL;
sixel_dither_t *dither = NULL;
// Copy BGR 3ch
c.width = src.cols;
c.height = src.rows;
c.skip = c.width * 3;
c.buf = new unsigned char[ c.height * c.skip ];
{
for ( int iy = 0; iy < c.height; iy++) {
unsigned char *p = src.ptr<unsigned char>(iy);
memcpy(
c.buf + iy * c.skip,
&(p[0]),
c.skip );
}
}
// Create output
status = sixel_output_new(&output, sixel_write, stdout, NULL);
if (SIXEL_FAILED(status)){
perror("sixel_output_new()");
goto end3;
}
// Create dither with VT340 COLOR
dither = sixel_dither_get(SIXEL_BUILTIN_VT340_COLOR);
sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_BGR888);
// Output Image
status = sixel_encode(c.buf, c.width, c.height, 0, dither, output);
if (SIXEL_FAILED(status)){
perror("sixel_encode()");
goto end;
}
end:
sixel_dither_destroy(dither);
sixel_output_destroy(output);
end3:
delete []c.buf;
}
int main(int argc, char **argv)
{
if ( argc == 2 ) {
cv::Mat test = cv::imread(argv[1], 3 );
std::cout <<" << Original Image >> " << std::endl;
show_to_sixel(test);
cv::blur ( test, test, cv::Size(10,10), cv::Point(-1, -1) );
// If you want to update in the same position, do call again.
// If you want to show next position, cout<<endl;
std::cout <<" << Filtered Image >> " << std::endl;
show_to_sixel(test);
}else{
std::cout << argv[0] << " filename " << std::endl;
}
return 0;
}
もし、もっと効果的にやるならば…
- ditherとかoutputの使いまわしとかも考えたほうがいいかなあ…
- dithered imageの扱いってどうしたらいいのかなあ、など等。
あと気を付ける事、かなあ?
OpenCVのバッファーは必ずしも連続しているとは限らないので、この実装みたいに「1行ずつコピー」をするのが無難だったはず。ちがってたらごめんねー。連続している時は丸ごとコピーっていう実装でもよかったかもしれないけど、まあ細かいことはキニシナイ!
まとめ
- TUI(Terminal User Interface)の表示手段として、libsixelが利用できることを確認した。
- 比較的簡単な方法でコンソール上に画面表示できることを確認できた。
以上になります!ありがとうございました。