AI玩游戏的一点尝试(6)—— 进度条处理(放弃AI,改用图像处理)

122 阅读4分钟

前言

AI玩游戏的一点尝试(1)—— 架构设计与初步状态识别

AI玩游戏的一点尝试(2)—— 初探无监督学习与特征可视化

AI玩游戏的一点尝试(3)—— 图片去重

AI玩游戏的一点尝试(4)—— 数字识别

AI玩游戏的一点尝试(5)—— 多样化的数字识别

目标说明

在游戏中,有个非常关键的信息:体力。体力在游戏中以彩色进度条的形式展现,没有标注具体数值。

image.png

选择使用哪种训练时,预计消耗的体力会以半灰色显示(在某些情况下会有额外的体力消耗,会以稍浅一点的灰色显示):

image.png

体力的最大值也可能增加,增加后体力条的宽度会变长:

image.png

通过游戏外信息得知,体力默认上限为100,在游戏中一次仅增长4体力上限,表现上非常不明显。

但是体力的多少对于游戏策略来说至关重要,在游戏中经常要选择这回合把体力消耗完接着下回合休息,还是这回合保留体力观察下回合情况这样的决策。因此有必要拿到准确的体力数据输入给模型。

通过AI模型识别可能的方式

离散化

对于不好判断实际数据的信息,通常会先离散化再表示。比如把体力按照颜色分为蓝色、绿色、黄色、红色四段,分别编码为0、1、2、3作为输入。

这种方法虽然利于标注,但是不能很好的顾及游戏策略:在游戏中体力的消耗是动态变化的,区间的范围太过广泛精度太低,不利于后续的学习过程。

根据训练失败率建立线性回归

游戏中体力与实际操作相关的信息只有训练的失败率,在常规情况下可以以失败率作为间接监督训练一个小型回归模型。但是在游戏中基本当体力在50%以下时失败率才>0,所以用这个方法无法预测体力>50%的情况。

在无状态的决策模型中,这不构成一个问题:模型只需要知道当前训练的失败率就可以给出当前的行动,而不需要知道体力的具体数值;

但在使用状态序列模型的强化学习中,体力的具体数值可以帮助模型决策以在未来产生更大的收益(比如假设这回合把体力用完,下回合训练属性加成很高是不是就亏了),无法推断具体数值也不利于后续学习过程。

其他方式

观察进度条发现,体力最大值增长时是往右侧扩展的,因此体力进度条起点是个固定的位置。在进度条的终点处固定有一个白色边框,编写脚本查看具体像素数据:

output_img = Image.new('RGB', (width * 50, 15), color='white')
draw = ImageDraw.Draw(output_img)

for i in range(width):
    color = pixels[i]
    draw.rectangle([i * 50, 0, (i + 1) * 50 - 1, 4], fill=color)
    font = ImageFont.truetype("simhei.ttf", 10)
    draw.text((i * 50, 5), str(v_values[i]), fill='black', font=font)

output_img.show()

通过像素分析得知进度条终点固定为#FFFFFF:

image.png

确定获取长度的方式后,下一步是要获取进度条的分界线,观察发现进度条底色是固定的灰色:

image.png

识别出进度条长度和当前值后,从策略上考虑知道训练的预估消耗值也是有帮助的。对于明暗关系变化的图像,可以用hsv格式进行处理,hsv中的v就表示了明度:

image.png

def _judge_stamina_bar(image):
    for i in range(width):
        if pixels[i] == (255, 255, 255):
            end_pos = i
            break
    if end_pos is None:
        raise Exception("体力条识别失败: 未找到体力条")
    current_stamina_pos = None
    for i in range(width):
        if pixels[i] == (116, 118, 116):
            current_stamina_pos = i
            break
    if current_stamina_pos is None:
        current_stamina_pos = end_pos

    hsv_pixels = [cv2.cvtColor(np.uint8([[pixel]]), cv2.COLOR_RGB2HSV)[0][0] for pixel in pixels]
    decrease_pos = None
    for i in range(current_stamina_pos):
        hsv = hsv_pixels[i]
        if hsv[2] < 230:
            decrease_pos = i
            break
    increase_pos = None
    for i in range(current_stamina_pos):
        hsv = hsv_pixels[i]
        if hsv[1] < 180:
            increase_pos = i
            break

    return current_stamina_pos, end_pos, decrease_pos, increase_pos

成果

直接使用图像识别的结果更精确也更省事的多:

image.png

image.png

对于个人开发者来说,我觉得应该合理的设计AI架构而不是盲目使用AI。在没有大量数据的情况下,应该尽可能的精简、抽象把不可读的图像数据经过预处理转为易读的状态数据再进行训练,才可以取得更好的效果。

下一步

对于核心的决策模型来说,没有足够的数据量支持我直接使用强化学习进行训练。我更希望先使用监督学习训练一个基础模型,再以此为基础进行强化学习。既然需要监督学习那么必须要有足够的数据集。下一步计划以此为基础完善数据收集与标注方式。