OpenCVのmat画像をコンソール上に表示してみた！(AAとかではないですよ）

#TL;DR：mat画像をコンソール上に表示できることを確認しました。

- TUI(Terminal User Interface)の表示手段として、libsixelが利用できることを確認した。
- 比較的簡単な方法でコンソール上に画面表示できることを確認できた。


# はじめに：ふと思ったのです、コンソールに画像出せたらちょっと幸せ、かもと。

- OpenCVで画像確認するときにわざわざ`window表示`とかメンドクサイ。
- gtk3とかインストールするのもメンドクサイ。
- だったら、matをそのままコンソール画面上に表示するにはどうしたらいいのか！？

# コンソールに画像を出すにはどうするのか　-> libsixelを使えばよい

コンソール上で画像を表示する方法としては、libsixelを用いるとよい、らしい。
それではこれで遊んでいく！なお、ライセンスはMITライセンス、と書かれている。

[libsixel](https://saitoha.github.io/libsixel/) ですかね！

（え？出力結果をjpgで書き出してこのツール通せばいい、そんな野暮な話はやめてください、泣きます）

# 開発環境

![OpenCV-next](https://img.shields.io/badge/OpenCV-next-brightgreen)  ![Ubuntu-2104](https://img.shields.io/badge/Ubuntu-21.04-brightgreen) ![mlterm-3.9.0](https://img.shields.io/badge/mlterm-3.9.0-brightgreen)


# ライブラリインストール

```bash
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では画面表示ができたので、これを使っていく！

https://u10e10.hatenablog.com/entry/2016/12/14/033342

|   | 表示可能？ |
|:-:|:-:|
| 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画像をコンソール表示はできた！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/53dedb08-d405-0963-ee82-bbf1d228b3dc.png)

## 確かにこれでもいいんですけどね……

`system()`関数使って、img2sixelで画像を表示させてもいいんですけどね、、、　やっぱりなんか残念な感じなので、プログラマらしく「不要な努力」かましたいです！

# ライブラリのつなぎ方を確認する

どうやらこのあたりにファイルがインストールされたらしい。
お、pkg-configにも繋がっている、これは勝ちましたかね！！

```bash
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
```

```bash
kmtr@ubuntu2104opencv:~/work/sixel$ pkg-config libsixel --cflags --libs
-I/usr/include/sixel -lsixel
kmtr@ubuntu2104opencv:~/work/sixel$ ls /usr/include/sixel/
sixel.h
```

## サンプルコードを見ながら解析

初期化コードはここからですねー

https://github.com/saitoha/libsixel/blob/master/examples/drawing/main.c#L217

```c
    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;
```

画面表示はここから。

https://github.com/saitoha/libsixel/blob/master/examples/drawing/main.c#L405

```
                            status = sixel_encode(c.buf, c.width, c.height, 0, dither, output);
                            if (SIXEL_FAILED(status)) {
                                goto end;
                            }
```

# 動作結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/24f18fae-d27f-81a6-964a-28f03957437a.png)


# サンプルコード

## 先にライセンスの話を…

当該サンプルコードの一部はlibsixelのサンプルコードをベースとしております。こちらは、下記の通りCC0となっております、

https://github.com/saitoha/libsixel/tree/master/examples/drawing

>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;`  で画像表示までやりたかったのですが、 ~~めんどくさ~~ 　複雑になりすぎるので、とりあえずこれで完成とします！！

```makefile:Makefile
a.out : a.cpp
	g++ a.cpp `pkg-config opencv5 libsixel --libs --cflags `

```


```cpp:a.cpp
#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のバッファーは必ずしも連続しているとは限らないので、この実装みたいに「１行ずつコピー」をするのが無難だったはず。ちがってたらごめんねー。連続している時は丸ごとコピーっていう実装でもよかったかもしれないけど、まあ細かいことはｷﾆｼﾅｲ！


# まとめ
- TUI(Terminal User Interface)の表示手段として、libsixelが利用できることを確認した。
- 比較的簡単な方法でコンソール上に画面表示できることを確認できた。


以上になります！ありがとうございました。