🐟

画像×視線データの可視化(個別データの解析)

に公開

📌はじめに

Eye Tracking(視線追跡)デバイスで取得した視線データをPythonで処理し、
ユーザーが広告の「どこ」を「どれくらい」見ていたのかを可視化する方法を紹介します。

視線データを活用すれば、画面上でユーザーの関心が集まるエリアを 定量的に把握 できます。
本記事では、視線の座標情報と滞在時間をもとに、広告画像の上へ 視線の軌跡や注目ポイントを重ね描画する手法 を解説します。



視線の時間と順番をマーカーで可視化した例
(マーカーの大きさ=見ていた時間、番号=見た順番)

❓Eye Tracking(アイ・トラッキング)とは❓

ユーザーが「どこを見ているか」を計測する技術。
専用デバイスやカメラで目の動き・視線位置をリアルタイムに捉えます。
広告やUI/UX研究、学習行動分析など幅広い分野で利用されています。


📌使用するデータ

  • 視線データ : sample.csv
    ユーザーの視線位置と、その場所に「どれくらいの時間」注目したかを記録したデータです。
    実験では、動画の途中で広告を表示し、
    興味がある部分はじっと見てもらい、興味がなければ視線を外してもらいました。

image.png
sample.csv

  • 広告画像 : fish.png(サイズ: 1360×840 ピクセル)
    分析対象となる広告画像です。

📌コード解説

#=============================================
# 0.ライブラリ
#=============================================
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.image as mpimg
import japanize_matplotlib

#=============================================
# 1.入力データ読み込み
#=============================================
df = pd.read_csv("サンプル.csv",encoding='UTF-8')

#=============================================
# 2.近い点をまとめる関数(前の点と距離20以内なら同じ座標に)
#=============================================
def merge_close_points(x, y, threshold=20):
    # 近い点をまとめる
    merged_x = []
    merged_y = []
    for i in range(len(x)):
        if i == 0:
            merged_x.append(x[i])
            merged_y.append(y[i])
        else:
            dx = x[i] - merged_x[-1]
            dy = y[i] - merged_y[-1]
            dist = np.sqrt(dx*dx + dy*dy)
            if dist <= threshold:
                merged_x.append(merged_x[-1])
                merged_y.append(merged_y[-1])
            else:
                merged_x.append(x[i])
                merged_y.append(y[i])

    # 連続するグループ番号の付与
    group_numbers = []
    group_id = 0
    for i in range(len(merged_x)):
        if i == 0:
            group_numbers.append(group_id)
        else:
            if merged_x[i] != merged_x[i-1] or merged_y[i] != merged_y[i-1]:
                group_id += 1
            group_numbers.append(group_id)

    return merged_x, merged_y, group_numbers

#=============================================
# 3.座標リスト
#=============================================
merged_x, merged_y, group_numbers = merge_close_points(df['position_x'], df['position_y'])

0.ライブラリ

  • pandas: CSVの読み込みやデータ処理に使用
  • numpy: 数値計算や距離の計算に使用
  • matplotlib.pyplot: グラフ描画に使用
  • matplotlib.image: 画像の読み込みに使用
  • japanize_matplotlib: グラフで日本語を正しく表示するために使用

1.入力データ読み込み

  • sample.csvを読み込みます

2. 近い点をまとめる関数

  • merge_close_points: 視線座標リスト (x, y) を入力として受け取り、
    まとめた座標 (merged_x, merged_y) とグループ番号 (group_numbers) を
     出力する関数
merge_close_points の役割と仕組み

目的

視線データはノイズや揺れで座標が細かくバラけがちです。
隣接する点同士が20ピクセル以内なら「同じ注目ポイント」とみなし、まとめています。

動作

前の点と現在の点の距離を計算し、閾値以下なら前の座標に揃えます。
座標が変わったときにグループ番号を更新し、同じ場所を見ていた点をひとまとまりに扱います。

なぜ20ピクセルなの❓

実際の視線は微妙にぶれるため、細かい揺れを無視するための基準として20ピクセル程度を採用。
ただし画面サイズやデータ特性によって最適値は変わるため、環境に応じて調整可能です。

3.座標リスト

  • merged_x, merged_y: まとめ後の座標リスト
  • group_numbers: 連続して同じ場所を見た点に同じ番号を付与し、滞在時間集計や可視化に利用できる
group_numbers の活用イメージ

連続した同じ座標に属する点をまとめて扱えるので、滞在時間の集計や色分けに利用できます。
可視化時に同じ注目エリアとして強調できるメリットがあります。


#=============================================
# 4. 画像読み込みと視線軌跡描画のポイント
#=============================================
img = mpimg.imread("fish.png")

plt.figure(figsize=(13.8, 8.6))
plt.imshow(img)


#=============================================
# 5. 視線軌跡と点の描画
#=============================================
# 軌跡の線(黒)
plt.plot(merged_x, merged_y, color='black', linewidth=1)

# 点の大きさと色
sizes = df['time'] * 10
colors = df['time']

scatter = plt.scatter(merged_x, merged_y, s=sizes, c=colors, cmap='viridis', alpha=0.8)

plt.colorbar(scatter, label='time')

#=============================================
6. 軸設定とグループ番号表示
#=============================================
plt.xlim(0, img.shape[1])
plt.ylim(img.shape[0], 0)  # Y軸反転

plt.xlabel("position_x")
plt.ylabel("position_y")
plt.title("視線軌跡 with Gradient Color and Labels")

for x, y, g, s in zip(merged_x, merged_y, group_numbers, sizes):
    fontsize = np.sqrt(s)  # 面積の平方根を文字サイズに
    plt.text(x, y, str(g), color='red', fontsize=fontsize, ha='center', va='center')

plt.show()

4. 画像読み込みと表示

  • img = mpimg.imread("fish.png"): 分析対象の広告画像を読み込み
  • plt.figure(figsize=(13.8, 8.6)): 描画領域のサイズを設定
  • plt.imshow(img): 画像を背景として表示
補足
  • mpimg.imreadで広告画像を読み込み、plt.imshowで背景として表示
  • 描画領域のサイズを画像の縦横比に合わせて調整
  • この段階ではまだ視線の軌跡や点は表示されていません

5. 視線軌跡と点の描画

  • plt.plot(merged_x, merged_y, color='black', linewidth=1): 視線軌跡を線で描画

  • plt.scatter(merged_x, merged_y, s=sizes, c=colors, cmap='viridis', alpha=0.8): 視線点を描画

    • sizes = df['time'] * 10: 点の大きさは滞在時間に比例
    • colors = df['time']: 点の色は滞在時間に応じて変化
    • alpha=0.8: 重なっても見やすくする
  • plt.colorbar(scatter, label='time'): 滞在時間の凡例を表示

補足
  • plt.plotで軌跡(線)を黒色で描画
  • plt.scatterで視線の位置を点として描き、大きさと色で滞在時間を表現
  • カラーマップviridisplt.colorbarで注目度を視覚化

6. 軸設定とグループ番号表示

  • plt.xlim(0, img.shape[1]) / plt.ylim(img.shape[0], 0): 画像座標系に合わせる(Y軸反転)
  • plt.xlabel("position_x") / plt.ylabel("position_y") / plt.title(...): 軸ラベルとタイトル設定
  • 点の中心にグループ番号を表示

❓なぜY軸を反転しているのか❓
  • 画像データは左上が原点で、Y軸の数値は下方向に増える
  • matplotlibの座標系は左下が原点で、Y軸は上方向に増える
  • plt.ylim(img.shape[0], 0) で反転させることで、画像と視線点の位置を正しく対応
点の大きさ・色で滞在時間を表現している理由
  • 長く注目した点は大きく、色も濃くして目立たせる
  • alpha=0.8 にして、点が重なっても見やすくする
グループ番号を点の中心に表示する工夫
  • group_numbers で同じ注目エリアをグルーピング
  • 文字サイズは点の大きさの平方根で調整し、バランスよく表示

7. 可視化結果のまとめ

  • 今回は1人分の視線データを、広告画像上に軌跡と注目点として可視化しました。
  • 点の大きさ・色で滞在時間を表現し、グループ番号で同じ注目エリアを識別可能です。
  • この手法により、ユーザーがどの部分を注目したか、注目の強弱、順序を直感的に把握できます。
補足
  • 黒い線で視線軌跡を表示することで、どの順番で画面を見たかがわかる
  • 点の大きさや色で滞在時間を表すことで、ユーザーの注目度を可視化
  • グループ番号を表示することで、視線のまとまり(注目エリア)が明確になる

📌まとめ

本記事ではPythonを使って1人分の視線データを広告画像の上に重ね、注目ポイントを可視化する手法を紹介しました。

次回は複数ユーザーの視線データを画像上に重ねて表示し、全体の注目傾向を把握する方法を紹介します。
https://zenn.dev/swatchp/articles/31ba02f1ab46c7


📌参考リンク・素材について

画像素材

掲載している画像素材は「いらすとや」さんのものを加工して使っています。

GitHubリポジトリ

本記事で紹介したコードやサンプルデータはこちらのリポジトリで公開しています。
 https://github.com/iwakazusuwa/image-gaze-visualizer-mit-license

Discussion