我最喜欢的YouTuber之一,CodeBullet,曾经试图创建一个PingPongAI来统治他们。这似乎是一个非常有趣和简单的任务,所以我想我也试一试。在这篇文章中,我将概述一些我考虑过的因素,如果你想从事任何类似的项目,这些因素可能会有所帮助,并且我想我会尝试其他一些因素,所以如果你喜欢这种类型的东西,你可以跟着我。使用计算机视觉的好处是我可以使用已经构建的游戏并处理图像。话虽如此,我们将使用CodeBullet在ponggame.org上使用的相同版本的游戏。它还具有2人游戏模式,因此我可以与自己的AI对战;我做到了,这真的很难...捕获屏幕您要做的第一件事就是捕获屏幕。我想确保我的帧率尽可能快,我发现MSS是一个很棒的python包。有了这个,我很容易达到60fps,而PIL我只能达到20fps。它作为一个numpy数组返回。桨检测为了简单起见,我们需要定义桨的位置。这可以通过几种不同的方式完成,但我认为最明显的是屏蔽每个桨的区域,然后运行连接的组件来找到桨对象。下面是一段代码:defget_objects_in_masked_region(img,vertices,connectivity=8):''':returnconnectedcomponentswithstatsinmaskedregion[0]retvalnumberoftotallabels0isbackground[1]labelsimage[2]stats[0]leftmostx,[1]topmosty,[2]horizo??ntalsize,[3]verticalsize,[4]area[3]centroids'''mask=np.zeros_like(img)#fillthemaskcv2.fillPoly(mask,[vertices],255)#nowonlyshowtheareathatisthemaskmask=cv2.bitwise_and(img,mask)conn=cv2。connectedComponentsWithStats(mask,connectivity,cv2.CV_16U)returnconn上面,“顶点”只是定义屏蔽区域的坐标列表。一旦我在每个区域内都有对象,我就可以获得它们的质心位置或边界框。需要注意的一件事是,OpenCV将背景作为任何连接组件列表中的第0个对象,因此在这种情况下,我总是得到第二大对象。结果如下-右侧带有绿色质心的球拍是玩家/即将成为AI控制的球拍。移动桨现在我们有了一个输出,我们需要一个输入。为此,我求助于其他人提供的有用包和代码。它使用ctypes来模拟键盘按下,在这种情况下,游戏是用“k”和“m”键玩的。我这里有扫码。测试它只是随机上下移动后,我们就可以开始跟踪了。乒乓球检测下一步是识别和跟踪乒乓球。同样,这可以通过几种方式处理——其中之一可以通过使用模板进行对象检测,但是,我再次使用连接的组件和对象属性,即乒乓球的面积,因为它是唯一具有维度对象。我知道每当乒乓球穿过或击中其他白色物体时我都会遇到问题,但我也认为只要大部分时间我都能追踪到它,一切都很好。毕竟,它是直线移动的。如果您观看下面的视频,您可以看到标记乒乓球的红色圆圈是如何闪烁的。这是因为它只能在每2帧中找到一个。在60fps下,这并不重要。用于反弹预测的光线投射此时我们已经有了一个可以工作的AI。如果我们只是移动玩家的球拍,使其与乒乓球处于相同的y轴位置,效果会很好。然而,乒乓球在弹跳良好时确实存在问题。桨太慢跟不上,需要预测乒乓球的位置,而不是仅仅移动到当前位置。这已在上面的剪辑中实现,下面是两种方法的比较。差异并不大,但如果选择了正确的AI,那绝对是更稳定的胜利。为此,我首先为乒乓球创建了一个位置列表。公平地说,我将此列表的长度保持为5,基本上是这样。不要将列表列得太长,否则需要更长的时间才能注意到它改变了方向。获得位置列表后,我使用简单的矢量平均来平滑并获得方向矢量-如绿色箭头所示。这也被归一化为一个单位向量,然后乘以一个长度以便于可视化。投射光线只是对此的延伸——使前向投影更长。然后我检查未来的位置是否在顶部和底部区域的范围之外。如果是这样,它只是将位置投射回游戏区域。对于左侧和右侧,它计算与桨的x位置相交的位置并将x和y位置固定到该点。这确保桨指向正确的位置。没有它,它通常会走得太远。下面是定义预测乒乓球未来位置的射线的代码:defpong_ray(pong_pos,dir_vec,l_paddle,r_paddle,boundaries,steps=250):future_pts_list=[]foriinrange(steps):x_tmp=int(i*dir_vect[0]+pong_pos[0])y_tmp=int(i*dir_vect[1]+pong_pos[1])ify_tmp>boundaries[3]:#bottomy_end=int(2*boundaries[3]-y_tmp)x_end=x_tmpelify_tmp
