有趣的鼠标位置规律及其分析与实现

&

 

如果把一天下来鼠标的位置画成一张热度图,结果会怎么样?上面这张图解答了这个问题。虽然这一次用的数据不是特别多,只有约3000次采样(5秒一次),但还是可以看出来一些东西的。当然,如果数据更多的话效果应该会更好。 

其中很明显的一个特征就是鼠标在右上角的概率特别高。这个很容易理解,经常去点那个小叉叉造成的嘛。但是为什么屏幕右边会有一列绿色呢?思考了一下,发现这可能因为我是用右手操作鼠标的。自己实际操作一下可以发现,如果是用右手握鼠标的话,光标很容易向右移过掉,直到在右侧屏幕边缘被挡住。这样光标就在相对较长的时间内滞留在屏幕右边。但同样的现象很难在左侧发生。因此仅在屏幕右边出现这样的密集区域。这个发现挺有意思的,似乎可以对HCI设计做出一些指导。但不晓得这样的分析是否有道理,希望大家指正。

此外,屏幕下方的一条显然是任务栏所致。左下角没有什么记录是因为我喜欢用Win键而不是用手点开始菜单。任务栏上只有一个地方是黄色,经过对照发现这里是任务管理器的位置所在。由于我个人习惯保持一个任务管理器的窗口并经常点开看看,记录图上才有此反映。

看来这个方法还可以观察到用户的一些小习惯呢。所以就像上篇文章说的,数据就是价值,而我们往往错过了太多的数据…

下面的部分是介绍这个小实验的实现。

总的来说思路很简单,首先记录鼠标位置,然后将其画成一个热度图即可。
关于获取鼠标位置,Windows有一个API: GetCursorPos()可以做这个事情。所以写一个简单的C++程序即可完成这个任务:

POINT point;
do
{
GetCursorPos(
&point);
cout
<< "(gt;" << point.x << "," << point.y << ")" << endl;
Sleep(
5000);
}
while(true);

我们可以用PowerShell建一个后台任务,配合管道来保持时刻监控鼠标:

Start-job {GetMousePos.exe > Mouse.txt}

关于绘制热度图,由于我没有找到现成的库或者软件,MATLAB似乎可以,但还没有装好而且似乎效果比较粗糙,所以用C# + OpenCV自己写了一个。算法也不是很复杂,一共只有3步。第一步统计各个点的鼠标停留次数,第二步为了图形的自然美观做了一次大半径的高斯模糊,第三部将黑白的图像进行着色,变成一个彩色的热度图。OpenCV部分用的是EmguCV这一.NET封装。C#代码如下,供参考。

 

static void Main(string[] args)
{
//parse parameters
if (args.Length != 1 && args.Length != 2)
{
Console.WriteLine(
"Parameter error!\r\nUsage:Create heat diagram from data of input file.\r\nHeatVisualizer <InputFilePath> [OutputImageFilePath]");
}

var pointCount
= new Dictionary<Point, int>();
Console.WriteLine(
"Please input data in (x, y) format.");
var pointStrings
= File.ReadAllLines(args[0]);

foreach (var pointString in pointStrings)
{
var match
= Regex.Match(pointString, @"\((\d+),\s*(\d+)\)");
if (match.Success)
{
var point
= new Point(int.Parse(match.Groups[1].Value) / 3, int.Parse(match.Groups[2].Value) / 3);
if (pointCount.ContainsKey(point))
{
pointCount[point]
++;
}
else
{
pointCount.Add(point,
1);
}
}
else
{
Console.WriteLine(
"Error format!");
return;
}
}

//smooth with OpenCV
const int screenWidth = 1280 / 3 + 1, screenHeight = 1024 / 3 + 1;
int maxCount = pointCount.Max(x => x.Value) / 10;
var image
= new Image<Gray, byte>(screenWidth, screenHeight);
foreach (var keyValuePair in pointCount)
{
image[keyValuePair.Key.Y, keyValuePair.Key.X]
= new Gray((double)keyValuePair.Value * 0xff / maxCount);
}
var image2
= new Image<Gray, byte>(screenWidth, screenHeight);
CvInvoke.cvSmooth(image.Ptr, image2.Ptr, Emgu.CV.CvEnum.SMOOTH_TYPE.CV_GAUSSIAN,
25, 25, 0, 0);
CvInvoke.cvEqualizeHist(image2.Ptr, image.Ptr);

//visualize
//calculate threshold
int yellowCount = (int)(maxCount * 0.6 + 0.5);
int yellowLength = maxCount - yellowCount;
int greenCount = yellowCount / 2;
int greenLength = yellowCount - greenCount;
int whiteLength = greenCount;

//colorize
var resultImage = new Image<Bgr, byte>(screenWidth, screenHeight);
for (int x = 0; x < image2.Width; x++)
{
for (int y = 0; y < image2.Height; y++)
{
int color = (int)image2[y, x].Intensity;
if (color >= yellowCount)
{
int green = (int)(0xff - (color - yellowCount) * 0xff / yellowLength);
resultImage[y, x]
= new Bgr(0, green, 0xff);
}
else if (color >= greenCount)
{
int red = (int)((color - greenCount) * 0xff / greenLength);
resultImage[y, x]
= new Bgr(0, 0xff, red);
}
else
{
int red = (int)(0xff - (color) * 0xff / whiteLength);
resultImage[y, x]
= new Bgr(red, 0xff, red);
}
}
}

//write to file
ImageViewer.Show(resultImage.Clone(), "ResultImage");
if (args.Length == 2)
{
resultImage.Save(args[
1]);
}

//data sweep
resultImage.Dispose();
image.Dispose();
image2.Dispose();
}

 

最后感谢Stephenjy同学提供灵感!

posted on 2010-03-01 00:57  grapeot  阅读(1909)  评论(11编辑  收藏  举报

导航