Pythonで画像ファイルViewer＋αを作ってみた

## はじめに

**Qiita**の久しぶりの記事投稿になります。

最近、本業の業務用として、**Python**でツール的プログラムを作る機会がありまして、そのときに、画像を表示したり、その画像に関してちょっとしたグラフ表示などを実現したので、そのあたりの処理を切り出して、画像ファイルViewer +αにしてみましたので、それを紹介してみようと思います。

## GUIライブラリは

まず、**Python**で作り始めたツールで、**GUI**を何で書こうかなと考えて、**Python**の**GUI**ライブラリには、

* [Tkinter](https://docs.python.org/3/library/tkinter.html "tkinter — Python interface to Tcl/Tk") ... Python標準のGUIライブラリ
* [PyQt](https://wiki.python.org/moin/PyQt "PyQt - Python Wiki") ... 名前から想像つくようにQtのPythonバージョン
* [PySimpleGUI](https://pypi.org/project/PySimpleGUI/ "PySimpleGUI - PyPI") ... Python用のシンプルなGUIライブラリ
* [wxPython](https://pypi.org/project/wxPython/ "wxPython - PyPI") ... wxWidgets(C++で記述されたクロスプラットフォームGUIツールキット)のPython版

などと色々とありますが、**Python**標準の**Tkinter**ではちょっと限界があるのかな、ネット上でぐぐってみると情報が多そうなのは**wxPython**かな、ということで、今回、**wxPython**を選んでみました。

## 作ったツールの内容

業務で普段は、画像のViewerとして、IrfanViewをよく使っています。また、ちょっと画像内の輝度値の分布などを見たいときは、ImageJをちょこちょこと使っています。

ゆくゆくは、そういうツールの代わりに使えるようなものにしたいな、っていうことで、今回、ひとまず、

* 画像ファイルをウィンドウに表示
* その輝度値の分布などを解析してグラフ表示

というところまで作ってみました。

## プログラム内の要素をいくつか説明

### 画像ファイルの表示

```
menu_bar = wx.MenuBar()

file_menu = wx.Menu()
open_menu_item = file_menu.Append(wx.ID_OPEN, "Open...", "Open an image file")
exit_menu_item = file_menu.Append(wx.ID_EXIT, "Quit", "Exit the application")
menu_bar.Append(file_menu, "File")
```

などと記述するだけで、Fileメニューに、**Open**及び**Quit**というメニュー項目を作ることができて、

```
self.Bind(wx.EVT_MENU, self.panel.on_browse, open_menu_item)
```
と、上記のopen_menu_itemに、以下の関数on_browseをバインドするだけで、期待通りに動いてくれます。

```
def on_browse(self, event):
    with wx.FileDialog(self, 'Select Image File', 
                        wildcard='Image files (*.png;*.bmp;*.tiff;*.jpg)|*.png;*.bmp;*.tiff;*.jpg', 
                        style=wx.FD_OPEN) as dialog:
        if dialog.ShowModal() == wx.ID_OK:
            self.load_image(dialog.GetPath())
```

### 輝度値の分布をグラフ化

今回、最初は、画像上でマウスでクリックした座標を拾って、その座標に対応する画素の輝度情報や、その水平位置における垂直方向の全画素の平均値[^1]を計算して、その値を単純に標準出力に出力する、などという形で作り始めていました。

[^1]: ラインセンサにより取得した画像を解析するために、垂直方向に平均を取るなどしている。

ですが、もっと、例えば、画像の拡大/縮小表示もしたい、グラフ表示などもしたい、などと考えているうちに、自分でいちから書くのはちょっと面倒だ、何かいいライブラリはないのかな(ありそうだ)、と思って探してみると。。。

[matplotlib](https://utokyo-ipp.github.io/appendix/5-matplotlib.html "Matplotlibライブラリ - Pythonプログラミング入門(東京大学 数理・情報教育研究センター)")というライブラリを見つけました。

以下のように、これぐらいのコード量で、水平の画素位置を横軸に、NumPyで算出した垂直方向の平均値を縦軸に、簡単にグラフ化することができました。

```
x_positions = np.arange(width)

red_channel_means = np.mean(image[:, :, 0], axis=0)
green_channel_means = np.mean(image[:, :, 1], axis=0)
blue_channel_means = np.mean(image[:, :, 2], axis=0)

fig, ax = plt.subplots(figsize=(10, 5))

ax.plot(x_positions, red_channel_means, label='Red Channel', color='red')
ax.plot(x_positions, green_channel_means, label='Green Channel', color='green')
ax.plot(x_positions, blue_channel_means, label='Blue Channel', color='blue')

ax.set_xlabel('Horizontal Position')
ax.set_ylabel('Mean Value')
ax.set_title(self.image_path)
ax.legend()

# マウスをあてた部分の座標、データを表示する
mplcursors.cursor(hover=True)

plt.tight_layout()
plt.legend()
plt.show()
```

このコードで作成できるグラフの画面の例を貼り付けておきます。

![white_image_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/998321/66310b52-093a-28c4-4b5b-55a85bd49bf5.png)

x=830の位置にちょっと特異点が見えていますが、この部分をグラフ上で拡大表示した状態が以下の画像です。このような拡大表示が、別に特に追加でコードを書かなくても標準的に実現されています。

![white_image_plot_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/998321/3bc7db1f-00b1-3f70-284c-d49500ac7cd6.png)


### 引数の処理

argparseを使っています。以下のような記述だけで、プログラムに引数を追加することができています。

```
parser = argparse.ArgumentParser(description='画像ファイルviewer')

# image_file位置引数にデフォルト値Noneを設定
parser.add_argument('image_file', default="", nargs='?', help='表示する画像ファイル')

args = parser.parse_args()
image_file_path = args.image_file
```

nargs='?'で、この引数が必須でないことを、そして、help=***でヘルプ表示での説明書きを、それぞれ指定しています。たったこれだけで、以下のようなヘルプ表示が用意されます。[]で囲われていることで、引数が必須ではないことが表現されていますね。

```
PowerShell> python3 .\MyImgViewer.py --help
usage: MyImgViewer.py [-h] [image_file]

画像ファイルviewer

positional arguments:
  image_file  表示する画像ファイル

options:
  -h, --help  show this help message and exit
```

## 成果物

今回、はじめて、GitHubで、[MyImgViewer](https://github.com/MakotoA67/ImageProcessing/blob/main/MyImgViewer/MyImgViewer.py "MyImgViewer at MakotoA67/ImageProcessing")という名前のプログラムとして公開してみました。

## おわりに

私は、根っからの(?)Cプログラマで、Pythonはまだまだ初心者ですが、最近は、Pythonでちょっとしたツールプログラムを作ることがあります。そして、Cで何でもいちから作っていた、私のような古い人間から見ると、Pythonの環境は、色々とライブラリが揃っていて便利ですね。常々、そう思います。

今回も、本業の業務上、ちょっと必要になったGUIツールを、Python + wxPythonで作っていたのですが、たまにはQiitaに投稿してみよう、ということで、だけど、それをそのまま見せて紹介するなどはやりにくいため、ちょっと汎用的な画像Viewerとして切り出して、その内容を紹介してみました。

このプログラムを機能拡張できたりしたときに、また投稿しようかな、と思います。それでは。