doupoa
一个不甘落后的热血青年!
Ping通途说

基于opencv在Python实现Minecraft钓鱼脚本

引言

近日,在服务器开荒的时候听到有玩家说要整潮涌之心去拆海底神殿。但潮涌之心需要8个鹦鹉螺合成。鹦鹉螺有三种方法获得,一是找拿着鹦鹉螺的溺尸,二是找流浪商人,三则是钓鱼。懒惰的我肯定懒得跑去找溺尸和流浪商人,并且花费大量的时间获得的附属物较少甚至没有。况且,找溺尸和流浪商人需要人工,钓鱼可以自动。但网上找不到自己满意的脚本或软件,于是就自己整一个吧。


1.事件分析

首先,我们来分析下鱼钩。

由图可以发现,BE的鱼钩是3D模型,而JE的鱼钩是永对玩家的贴图。

但更容易让人注意的是鱼钩为上下两条白线中间一条红线,在钓鱼时,鱼钩会浮在水面上,红色条纹的部分是露出在水面上的。当鱼上钩时,鱼钩会被拉入水中,红条消失

因此可以利用上述特性展开对钓鱼脚本的设计。

(其实还有一种设计思路,就是实时识别游戏中的“字幕”,当上钩时会显示特有的字幕“浮标:溅起水花”,在此字幕显示时就可以拉钩了。)

2.脚本编写

在开始之前,您需要安装以下库:

序号库名pip指令
1numpypip install numpy
2opencvpip install opencv-python
3msspip install mss
4pynputpip install pynput

2.1屏幕捕获

一开始,我们该如何实时获取屏幕的内容?这里我们使用mss库。该库本是用于捕获屏幕截图的库,但连续捕获也可以实现屏幕实时捕获的效果。

如果您安装了pip,您可以通过以下指令立即安装该库。

 python -m pip install -U --user mss

我们捕获的图像需要经过numpy进行矩阵转换,之后就可以送进opencv进行处理了。这种方法mss库官方也给出了详细用法

import time

import cv2
import mss
import numpy


with mss.mss() as sct:
    # Part of the screen to capture
    monitor = {"top": 40, "left": 0, "width": 800, "height": 640}

    while "Screen capturing":
        last_time = time.time()

        # Get raw pixels from the screen, save it to a Numpy array
        img = numpy.array(sct.grab(monitor))

        # Display the picture
        cv2.imshow("OpenCV/Numpy normal", img)

        # Display the picture in grayscale
        # cv2.imshow('OpenCV/Numpy grayscale',
        #            cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY))

        print("fps: {}".format(1 / (time.time() - last_time)))

        # Press "q" to quit
        if cv2.waitKey(25) & 0xFF == ord("q"):
            cv2.destroyAllWindows()
            break

但是我们不可能去捕获整个屏幕,一来CPU处理量增大了,二来干扰项也增多了。因此我们需要定义一个范围让mss库捕获就可以了。

https://doupoa.site/wp-content/uploads/2021/12/1640933876-2021-12-26_11.22.36-1024x529.png
红框为捕获范围

该框范围可让Python自行计算:


from mss import mss
import cv2


imgname = mss().shot()
print("正在获取您的屏幕..")
img = cv2.imread(imgname) #读取图像
sp = img.shape
szX = sp[1]
szY = sp[0]
print("当前您的屏幕尺寸为{}x{}".format(szX, szY))
point1 = (int(szX * 0.49), int(szY * 0.6))
point2 = (int(szX * 0.51), int(szY * 0.4))
bounding_box = {
    "top": int(szX * 0.15),
    "left": int(szX * 0.49),
    "width": int(point2[0] - point1[0]),
    "height": int(point1[1] - point2[1]),
}

我的屏幕尺寸是1920x1080,在这里先通过mss库对全屏截图,再让opencv读取后即可获得当前屏幕的尺寸。

随后需要定义范围供之后持续捕获屏幕使用,如果您仔细观察point1和point2的值是如何获得的,您就会发现以该值为长宽基点,所画出的矩形跟之前图片中所示范围较为相近。最后只需要再给mss库定义截图范围就可以了。(该值在1920x1080的屏幕中可用,其他尺寸屏幕未测试,若有偏差,需要手动对上述值进行调整。)

2.2图像处理

当我们拿到截图范围之后,我们就可以开始对该范围的内容进行捕获、处理了。

经过mss库捕获的屏幕图像预计能有30帧左右,足以让图像提供相关数据进行逻辑分析。

直接来看看代码:

while True:
    mss_img = np.array(mss().grab(bounding_box))  # 持续捕获屏幕
    hsv = cv2.cvtColor(mss_img, cv2.COLOR_BGR2HSV)  # 转成hsv模式提取红色特征
    minRed = np.array([0, 43, 46]) #定义红色最小值
    maxRed = np.array([10, 255, 255]) #定义红色最大值
    mask = cv2.inRange(hsv, minRed, maxRed) #创建蒙版
    ret, binary = cv2.threshold(mask, 100, 255, cv2.THRESH_BINARY)  # 二值化
    contours, hierarchy = cv2.findContours(
        binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_TC89_L1
    )  # 识别轮廓
    cv2.drawContours(mss_img, contours, -1, (0, 0, 255), 1) #画出轮廓
    cv2.imshow("img", binary) #展示图像

   if (cv2.waitKey(1) & 0xFF) == ord("q"): #按"q"停止捕获并结束脚本
        cv2.destroyAllWindows()
        break

每一句代码后都带有注释,因此不必过多再解释代码含义,但有几个需要再说明一下。

1.直接捕获的图像不能直接使用,需要经过numpy对图像转换成BGR图像模式的矩阵。BGR其实就是蓝色、绿色、红色,与RGB相近,只是换了下顺序。

2.HSV是一种人们在生活中甚至更常使用的颜色系统,因为它更符合人描述颜色的方式。H、S、V分别指色相、饱和度、色调,您可以从下图更加直观的看到HSV模式。

https://doupoa.site/wp-content/uploads/2021/12/1640937129-R-C.jpg

因此,我们可以根据人们已经整理好的HSV表设置我们要捕获的红色最小、最大区域:

https://doupoa.site/wp-content/uploads/2021/12/1640937306-OIP-C.jpg

3.二值化是图像分割的一种最简单的方法。 二值化可以把灰度图像转换成二值图像(即1和0)。二值化代码的上一行以红色为蒙版所输出的图像就已经是黑白图像了,此处二值化是生成相关数据给下一行代码使用。关于二值化更加详细的解释可以看这里

2.3逻辑处理

先来看代码:

cts = len(contours)
    if cts == 0:
        print("\n鱼钩消失,重新抛钩")
        mouse.click(Button.right)
        time.sleep(2)
        continue

        # 计算鱼钩高度
    hooktop = 9999
    hookbottom = 0
    for contour in contours:
        pts = len(contour)
        for i in range(0, pts):
            hooktop = min(hooktop, contour[i, 0, 1])
            hookbottom = max(hookbottom, contour[i, 0, 1])
    hookheight = hookbottom - hooktop

    print("\r鱼钩位置{}".format(hookheight), end="")
    if hookheight <= 0:
        print("\n收钩..")
        mouse.click(Button.right)
        print("抛钩..")
        mouse.click(Button.right)
        time.sleep(2)

此段代码实现的功能其实很简单,首先取得cv2.findContours()方法中第一个返回值,方法在图像中找到的轮廓,这里用len()函数即可取得轮廓数量。

当轮廓数量为零时,即可判断为鱼钩下沉或没投掷出鱼钩,随后触发鼠标右键甩出鱼钩。

当轮廓量大于零时,计算图片上层及下层到轮廓的距离,两值相减即可获得鱼钩当前位置。当位置低于零时即可判断鱼钩下沉,随后右键收杆。


3.成品

https://doupoa.site/wp-content/uploads/2021/12/1640941272-image-1024x424.png
实际效果 右侧框为可视化捕获窗口

如果您喜欢该脚本麻烦请给颗小星星吧!

赞赏

doupoa

文章作者

诶嘿

发表回复

textsms
account_circle
email

  • e

    可以根据“框范围”内是否存在红色像素点来判断鱼是否上钩

    3 年前 回复
  • ngiokweng

    GOOD

    3 年前 回复

Ping通途说

基于opencv在Python实现Minecraft钓鱼脚本
基于opencv在Python实现Minecraft钓鱼脚本
扫描二维码继续阅读
2021-12-31

Optimized by WPJAM Basic