【PySide6開発奮闘記】QLabelからQGraphicsViewへ!画像解析ツールのリファクタリング全記録

ALL

こんにちは、Tech Samuraiです!
今回は、私の新しい開発環境である Mac mini (M4) で、PythonとPySide6を使い、高機能な「画像カラーアナライザー」を開発した際の、試行錯誤の全記録をお届けします。

このツールの目的は、画像内の特定の矩形範囲に含まれるピクセルの色情報を詳細に分析(平均、中央値、最大/最小など)することです。当初はシンプルなQLabelで実装していましたが、途中で**「ズーム機能が欲しい」**という追加要件が発生。これにより、QLabel`では対応できない、より高度なQGraphicsView`フレームワークへと、大幅な設計変更(リファクタリング)を行うことになりました。

この開発プロセスを通して、PySide6の強力なグラフィックス機能と、その実装でハマったNameError`という典型的な罠、そしてその解決策までを詳しく探検します。


1. プロジェクトの要件と環境セットアップ

まずは、このツールの要件と、Ryeを使ったモダンなPython環境のセットアップです。

主な要件

  • コマンドライン引数、またはGUIのファイルダイアログから画像(JPG, PNG, HEIC)を読み込む。
  • 画像を表示し、ユーザーがマウスドラッグで矩形範囲を指定できる。
  • 指定された範囲のピクセルデータを解析し、「平均値」「中央値」「最も明るい/暗い色」を計算してターミナルに出力する。

ライブラリのセットアップ

Ryeプロジェクトのルートで、以下のコマンドを実行し、必要なライブラリをインストールします。

# PySide6 (GUIフレームワーク)
rye add PySide6

# Pillow (画像処理ライブラリ)
rye add Pillow

# pillow-heif (HEICファイル読み込み用)
rye add pillow-heif

# numpy (ピクセル計算の高速化・統計処理用)
rye add numpy

2. 最初の実装 (V1):`QLabel`を使ったシンプルなアプローチ

最初のバージョンは、「まず動くもの」を最優先し、最もシンプルなQLabel`をベースに作成しました。

  • QLabel`を継承したクラスを作成し、setPixmap()`で画像を表示。
  • mousePressEvent`, `mouseMoveEvent`, `mouseReleaseEvent`をオーバーライド(再定義)し、ドラッグ操作で選択範囲の矩形(QRect`)を描画。
  • マウスが離された時点で、QRect`の座標を元にPillowの.crop()`で画像を切り出し、NumPyでピクセル解析。

この実装で、基本的な要件はすべて満たせました。しかし、ここで新たな要望が。「**細かい部分を正確に選択したいので、画像を拡大・縮小したい**」と。


3. 設計変更 (V2):QGraphicsView`へのリファクタリング

QLabel`には、標準で簡単なズーム機能は搭載されていません。この要求に応えるため、PySide6のより高機能な**`QGraphicsView`フレームワーク**へと、設計を根本から変更する必要に迫られました。

  • QMainWindow`の導入: ツールバー(ボタン置き場)が必要になったため、メインウィンドウをQWidget`からQMainWindow`に変更。
  • QGraphicsView`の採用: QLabel`の代わりにQGraphicsView`を採用。画像をQGraphicsScene`上のQGraphicsPixmapItem`として配置することで、.scale()`メソッドで簡単に拡大・縮小が可能になります。
  • ズーム機能の実装: wheelEvent`をオーバーライドし、Ctrlキー(またはCmdキー)+マウスホイールでズームできるようにしました。また、QToolBar`に「拡大」「縮小」「全体表示」ボタンを配置しました。
  • 座標変換の必要性: QGraphicsView`を使ったため、マウスイベントで取得する座標(ビュー座標)をself.mapToScene(event.pos())`を使って、画像上の座標(シーン座標)に変換するという、追加の処理が必要になりました。

4. 開発奮闘記:NameError: name 'QPainter' is not defined

V2へのリファクタリングが順調に進み、いざ実行!…と思った矢先、以下のエラーでクラッシュしました。

NameError: name 'QPainter' is not defined.

発生箇所:

# ズームしたときに画像が滑らかになるよう、アンチエイリアシングを有効化
self.setRenderHint(QPainter.RenderHint.Antialiasing, True)

原因と解決策:
原因は、あまりにも単純なものでした。QGraphicsView`の描画品質を上げるためにQPainter`の機能を使おうとしたのに、**スクリプトの冒頭でQPainter`クラス自体をインポートし忘れていた**のです。

Pythonは「QPainter`という名前は定義されていないよ」と、当然のエラーを返してきたわけです。

修正前:

from PySide6.QtGui import QPixmap, QImage, QPen, QAction, QIcon

修正後:

# QPainter をインポートリストに追加
from PySide6.QtGui import QPixmap, QImage, QPen, QAction, QIcon, QPainter

この一行を追加することで、エラーは無事に解消され、ズーム機能付きの画像解析ツールが完成しました。


完成したスクリプトとリポジトリ

これらの試行錯誤を経て完成した、QGraphicsView`ベースの画像カラーアナライザーの全コードは、以下のGitHubリポジトリで公開しています。

import sys
import numpy as np
from PIL import Image
from PySide6.QtWidgets import (
    QApplication, 
    QFileDialog, 
    QMainWindow, 
    QGraphicsView, 
    QGraphicsScene,
    QGraphicsPixmapItem,
    QGraphicsRectItem,
    QToolBar,
    QSizePolicy
)
# ▼▼▼ 修正ポイント ▼▼▼
from PySide6.QtGui import QPixmap, QImage, QPen, QAction, QIcon, QPainter
from PySide6.QtCore import Qt, QRectF, QPointF

# HEICサポートをインポート時に有効化
try:
    import pillow_heif
    pillow_heif.register_heif_opener()
except ImportError:
    print("pillow-heifがインストールされていません。HEICファイルは読み込めません。")

# ... (関数の定義: format_hex, analyze_pixels) ...
# ... (クラスの定義: ZoomableGraphicsView, MainWindow) ...
# ... (関数の定義: get_image_path_gui, main) ...

# (スクリプト全文はリポジトリを参照)

if __name__ == "__main__":
    main()

まとめ

今回の開発は、シンプルなQLabel`での実装から、ズームという**機能要件の追加**によって、より複雑で強力なQGraphicsView`フレームワークへの**リファクタリング**を経験する、非常に良い学びの機会となりました。

GUIアプリ開発では、このように、後から機能を追加するために、土台となるクラスの設計から見直すことが多々あります。そして、そんな時ほど、NameError`のような単純なインポート忘れに足元をすくわれがちです。エラーを恐れず、一つずつ着実に解決していくことが、良いアプリケーションへの一番の近道ですね。

コメント

タイトルとURLをコピーしました