(著)山たー
注視点のヒートマップを作成する。
追記:自分で実装しました
→https://github.com/takyamamoto/Fixation-Densitymap
Matlabでの実装では
https://github.com/cvzoya/fixation-visualization
があるが、今回はPyGazeの実装を改変した
https://github.com/r0ehre/GazePointHeatMap
を使ってみた。
コード
gazeheatplot.py
import os
import argparse
import csv
import numpy as np
from matplotlib import pyplot, image
def draw_display(dispsize, imagefile=None):
"""Returns a matplotlib.pyplot Figure and its axes, with a size of
dispsize, a black background colour, and optionally with an image drawn
onto it
arguments
dispsize - tuple or list indicating the size of the display,
e.g. (1024,768)
keyword arguments
imagefile - full path to an image file over which the heatmap
is to be laid, or None for no image; NOTE: the image
may be smaller than the display size, the function
assumes that the image was presented at the centre of
the display (default = None)
returns
fig, ax - matplotlib.pyplot Figure and its axes: field of zeros
with a size of dispsize, and an image drawn onto it
if an imagefile was passed
"""
# construct screen (black background)
screen = np.zeros((dispsize[1], dispsize[0], 3), dtype='float32')
# if an image location has been passed, draw the image
if imagefile != None:
# check if the path to the image exists
if not os.path.isfile(imagefile):
raise Exception("ERROR in draw_display: imagefile not found at '%s'" % imagefile)
# load image
img = image.imread(imagefile)
# width and height of the image
w, h = len(img[0]), len(img)
# x and y position of the image on the display
x = dispsize[0] / 2 - w / 2
y = dispsize[1] / 2 - h / 2
# draw the image on the screen
screen[y:y + h, x:x + w, :] += img
# dots per inch
dpi = 100.0
# determine the figure size in inches
figsize = (dispsize[0] / dpi, dispsize[1] / dpi)
# create a figure
fig = pyplot.figure(figsize=figsize, dpi=dpi, frameon=False)
ax = pyplot.Axes(fig, [0, 0, 1, 1])
ax.set_axis_off()
fig.add_axes(ax)
# plot display
ax.axis([0, dispsize[0], 0, dispsize[1]])
ax.imshow(screen) # , origin='upper')
return fig, ax
def gaussian(x, sx, y=None, sy=None):
"""Returns an array of numpy arrays (a matrix) containing values between
1 and 0 in a 2D Gaussian distribution
arguments
x -- width in pixels
sx -- width standard deviation
keyword argments
y -- height in pixels (default = x)
sy -- height standard deviation (default = sx)
"""
# square Gaussian if only x values are passed
if y == None:
y = x
if sy == None:
sy = sx
# centers
xo = x / 2
yo = y / 2
# matrix of zeros
M = np.zeros([y, x], dtype=float)
# gaussian matrix
for i in range(x):
for j in range(y):
M[j, i] = np.exp(
-1.0 * (((float(i) - xo) ** 2 / (2 * sx * sx)) + ((float(j) - yo) ** 2 / (2 * sy * sy))))
return M
def draw_heatmap(gazepoints, dispsize, imagefile=None, alpha=0.5, savefilename=None, gaussianwh=200, gaussiansd=None):
"""Draws a heatmap of the provided fixations, optionally drawn over an
image, and optionally allocating more weight to fixations with a higher
duration.
arguments
gazepoints - a list of gazepoint tuples (x, y)
dispsize - tuple or list indicating the size of the display,
e.g. (1024,768)
keyword arguments
imagefile - full path to an image file over which the heatmap
is to be laid, or None for no image; NOTE: the image
may be smaller than the display size, the function
assumes that the image was presented at the centre of
the display (default = None)
alpha - float between 0 and 1, indicating the transparancy of
the heatmap, where 0 is completely transparant and 1
is completely untransparant (default = 0.5)
savefilename - full path to the file in which the heatmap should be
saved, or None to not save the file (default = None)
returns
fig - a matplotlib.pyplot Figure instance, containing the
heatmap
"""
# IMAGE
fig, ax = draw_display(dispsize, imagefile=imagefile)
# HEATMAP
# Gaussian
gwh = gaussianwh
gsdwh = gwh / 6 if (gaussiansd is None) else gaussiansd
gaus = gaussian(gwh, gsdwh)
# matrix of zeroes
strt = gwh / 2
heatmapsize = dispsize[1] + 2 * strt, dispsize[0] + 2 * strt
heatmap = np.zeros(heatmapsize, dtype=float)
# create heatmap
for i in range(0, len(gazepoints)):
# get x and y coordinates
x = strt + gazepoints[i][0] - int(gwh / 2)
y = strt + gazepoints[i][1] - int(gwh / 2)
# correct Gaussian size if either coordinate falls outside of
# display boundaries
if (not 0 < x < dispsize[0]) or (not 0 < y < dispsize[1]):
hadj = [0, gwh];
vadj = [0, gwh]
if 0 > x:
hadj[0] = abs(x)
x = 0
elif dispsize[0] < x:
hadj[1] = gwh - int(x - dispsize[0])
if 0 > y:
vadj[0] = abs(y)
y = 0
elif dispsize[1] < y:
vadj[1] = gwh - int(y - dispsize[1])
# add adjusted Gaussian to the current heatmap
try:
heatmap[y:y + vadj[1], x:x + hadj[1]] += gaus[vadj[0]:vadj[1], hadj[0]:hadj[1]] * gazepoints[i][2]
except:
# fixation was probably outside of display
pass
else:
# add Gaussian to the current heatmap
heatmap[y:y + gwh, x:x + gwh] += gaus * gazepoints[i][2]
# resize heatmap
heatmap = heatmap[strt:dispsize[1] + strt, strt:dispsize[0] + strt]
# remove zeros
lowbound = np.mean(heatmap[heatmap > 0])
heatmap[heatmap < lowbound] = np.NaN
# draw heatmap on top of image
ax.imshow(heatmap, cmap='jet', alpha=alpha)
# FINISH PLOT
# invert the y axis, as (0,0) is top left on a display
ax.invert_yaxis()
# save the figure if a file name was provided
if savefilename != None:
fig.savefig(savefilename)
return fig
##################
# Parsing #
##################
parser = argparse.ArgumentParser(description='Parameters required for processing.')
#required args
parser.add_argument('input-path', type=str, help='path to the csv input')
parser.add_argument('display-width', type=int, help='an integer representing the display width')
parser.add_argument('display-height', type=int, help='an integer representing the display height')
#optional args
parser.add_argument('-a', '--alpha', type=float, default='0.5', required=False, help='alpha for the gaze overlay')
parser.add_argument('-o', '--output-name', type=str, required=False, help='name for the output file')
parser.add_argument('-b', '--background-image', type=str, default=None, required=False, help='path to the background image')
#advanced optional args
parser.add_argument('-n', '--n-gaussian-matrix', type=int, default='200', required=False, help='width and height of gaussian matrix')
parser.add_argument('-sd', '--standard-deviation', type=float, default=None ,required=False, help='standard deviation of gaussian distribution')
args = vars(parser.parse_args())
input_path = args['input-path']
display_width = args['display-width']
display_height = args['display-height']
alpha = args['alpha']
output_name = args['output_name'] if args['output_name'] is not None else 'output'
background_image = args['background_image']
ngaussian = args['n_gaussian_matrix']
sd = args['standard_deviation']
with open(input_path) as f:
reader = csv.reader(f)
raw = list(reader)
gaza_data = []
if len(raw[0]) is 2:
gaze_data = list(map(lambda q: (int(q[0]), int(q[1]), 1), raw))
else:
gaze_data = list(map(lambda q: (int(q[0]), int(q[1]), int(q[2])), raw))
draw_heatmap(gaze_data, (display_width, display_height), alpha=alpha, savefilename=output_name, imagefile=background_image, gaussianwh=ngaussian, gaussiansd=sd)
オプションは実行時に記述する。
python gazeheatplot.py gaze-data.csv 1440 900 [-a 0.6] [-o output-name] [-b /Me/bg-image.png] [-n 200] [-sd 33]
注視点分布の可視化
注視点データを可視化してみる。MIT Saliency Benchmarkにあるデータセット"CAT2000"を使ってみる。
mat2csv.py
import scipy.io as sio
import numpy as np
#matdata全体の読み込み
matdata = sio.loadmat("./FIXATIONLOCS/Affective/001.mat")
fixLocs=matdata["fixLocs"]
#matファイルをメモリから消去する
matdata.clear()
#位置を取得
fixPos_tmp=np.where(fixLocs==1)
fixPos=np.zeros((369,2))
#x,yを入れ替え
fixPos[:,0]=fixPos_tmp[1]
fixPos[:,1]=fixPos_tmp[0]
fixPos=fixPos.astype(np.int64)
#csvとして出力
np.savetxt('Affective_001.csv',fixPos,fmt="%.0f",delimiter=',')
matファイルをcsvファイルに変換。
jpg2png.py
from PIL import Image
im = Image.open('./Stimuli/Affective/001.jpg')
im.save('./Stimuli/Affective/001.png', quality=95)
PILでjpgをpngにしておく。こうしないと色がおかしくなった。
結果
activate py27_32
python gazeheatplot.py Affective_001.csv 1920 1080 -a 0.6 -o output.png -b ./Stimuli/Affective/001.png -n 200 -sd 33
コメントをお書きください
和田哲也 (水曜日, 05 8月 2020 06:57)
参考にさせていただいております。
python3で動くように改変予定はないでしょうか?