65.9K
CodeProject 正在变化。 阅读更多。
Home

使用贝塞尔曲线和高斯分布模拟人类鼠标输入

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2014年4月14日

CPOL

3分钟阅读

viewsIcon

29172

downloadIcon

1030

使用贝塞尔曲线和高斯分布模拟人类鼠标输入

引言

有一天,你发现自己正在尝试编写一个应用程序,该应用程序假装是人类(即以可信的方式移动鼠标和点击按钮),这是解决问题的一种方法。

我能想到的使用此代码的理由很少(如果有的话),但也许老大哥正在监视你,而你宁愿去酒吧?

问题

在 .NET 中,将鼠标光标发送到屏幕上的某个位置非常容易

 System.Windows.Forms.Cursor.Position = new System.Drawing.Point(x, y); 

我们甚至可以直线移动它

for (int count = 1; count < 100; count++)
System.Windows.Forms.Cursor.Position 
            = new System.Drawing.Point(count, count); 

调用 Windows API 发送鼠标点击事件也相对容易,使用以下签名

[DllImport("user32.dll")]
public static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo); 

但普通人的手腕不会沿着直线移动,而且它所追踪的每一条线都会略有不同。再加上人类是不完美的,并且并不能总是第一次准确地将指针定位在目标上。因此,如果我们要自动化我们平凡的点击任务并同时欺骗老大哥,那么我们有两个不同的问题需要解决

  1. 生成弯曲的鼠标移动
  2. 在目标上分布半准确的鼠标点击

一个解决方案

  1. 与其使用简单的圆形弧线,我们可以合理地认为鼠标移动更好地由贝塞尔曲线表示。这里是一个关于贝塞尔曲线的很好的图形表示,对于我们的目的,我们将使用一个简单的二维二次贝塞尔样条。这确保了弧线的曲率沿其长度平滑变化。

    Tolga Birdal 撰写的优秀文章贝塞尔曲线变得简单介绍了我们将用于生成鼠标起始位置和目标位置之间中间点的算法。

  2. 为了模仿选择目标时的不准确性,让我们使用一个随机的正态分布,其中目标的坐标中心位于钟形曲线的顶部(即平均值)。

这里的问题是 BCL Random 类将在目标上生成一个均匀分布。不是很现实 (),所以让我们使用 Box-Muller 方法对其进行转换,如stackoverflow所示,如下所示

Random gaussian = new Random();
bool haveNextNextGaussian = false;
double nextNextGaussian;  

public double NextGaussian()
        {
                double v1, v2, s;
                do
                {
                    v1 = 2 * gaussian.NextDouble() - 1;   
                    v2 = 2 * gaussian.NextDouble() - 1;
   
                    s = v1 * v1 + v2 * v2;
                } while (s >= 1 || s == 0);
                double multiplier = Math.Sqrt(-2 * Math.Log(s) / s);
                nextNextGaussian = v2 * multiplier;
                
                return v1 * multiplier;
        } 

因此,让我们将其整合到一个函数中,该函数会将移动和点击的预期 X-Y 坐标转换为一系列近似点,然后我们可以使用这些点来驱动鼠标。

首先,让我们修改实际目标

public List<Point> MouseMoveAndClick(int x, int y)
{
int pointerAccuracy = 10;  
int targetX = x + Convert.ToInt32(pointerAccuracy * targetDistribution.NextGaussian());
int targetY = y + Convert.ToInt32(pointerAccuracy * targetDistribution.NextGaussian()); 

然后,我们必须推导出控制点(控制样条曲率的坐标),我任意选择它为一个向量(随机长度),该向量垂直于起点和目标之间的中点

//declare the original pointer position
int originalX = System.Windows.Forms.Cursor.Position.X;
int originalY = System.Windows.Forms.Cursor.Position.Y;

//find a mid point between original and target position
int midPointX = (x - targetX) / 2;
int midPointY = (y - targetY) / 2;

//Find a co-ordinate normal to the straight line between start and end point, starting at the midpoint and normally distributed
//This is reduced by a factor of 4 to model the arc of a right handed user.
int bezierMidPointX = Convert.ToInt32((midPointX / 4) * (midpointDistribution.NextGaussian()));
int bezierMidPointY = Convert.ToInt32((midPointY / 4) * (midpointDistribution.NextGaussian()));

有了我们的起始坐标、控制点和目标点,我们可以生成中间点

BezierCurve bc = new BezierCurve();
double[] input = new double[] 
{ originalX, originalY, bezierMidPointX, bezierMidPointY, targetX, targetY };
                
int numberOfDataPoints = 1000;
double[] output = new double[numberOfDataPoints];
                
//co-ords are couplets of doubles hence the / 2
bc.Bezier2D(input, numberOfDataPoints / 2, output);

int pause = 0;

然后,通过循环遍历坐标,我们可以修改鼠标位置。为了增加趣味性,我们甚至可以在我们的人类锁定目标时减慢移动速度

List<System.Drawing.Point> points = new List<Point>();
for (int count = 1; count != numberOfDataPoints - 1; count += 2)
{
    Point point = new System.Drawing.Point((int)output[count + 1], (int)output[count]);
    points.Add(point);
    System.Windows.Forms.Cursor.Position = point;

    //we can vary when we pause between moving from point to point, but to replicate how a user's action will slow down prior to clicking:
    if ((count % 10) == 0)
        pause = 100 + ((count ^ 5) / (count * 2));

    System.Threading.Thread.Sleep(pause);                    
} 

最后,将左键点击事件发送到 API

    //Use the Win32 API to send a mouse down/ mouse up events separated by an arbitrary pause
    mouse_event((int)(MouseEventFlags.LEFTDOWN), 0, 0, 0, 0);
    System.Threading.Thread.Sleep(50);
    mouse_event((int)(MouseEventFlags.LEFTUP), 0, 0, 0, 0);

    return points;
} 

结论

当然,要充分利用这种方法,您必须以编程方式发现您的模拟人类将要点击的目标的位置。也许这将是更广泛文章的主题,但现在请随意使用附带的示例进行试验。

历史

  • #1 代码和解释
© . All rights reserved.