深度学习训练模型:YOLOv5框架
图像识别:OpenCV
编程语言:python
IDE:Pycharm
运行环境:Pytorch+CUDA+Anaconda
这篇博客我不会特别具体的讲操作流程,更注重用到的一些函数和库之类的
实现方法大概是:实时截取屏幕,然后通过比对模型来识别截取图片上的CT,T阵营人物。然后获取坐标,传给我们自己写的函数,然后调用操控鼠标的库,实现开枪。
什么叫yolov5?其实就是 You Only Look Once 意思是这个框架简单到你看一次,或者上手试着用一次你就会了。
V5和V4没有递进关系,两个是独立的框架,不存在哪个最好哪个不好。
首先我们从GitHub上把yolov5的框架clone下来,为了方便,我们使用Ceaser的Csgo-yolov5模型,全是中文备注,更方便我们去训练模型
配置环境:
要在本地先查看你的CUDA版本
然后根据一系列信息在官网安装pytorch
首先先在csgo游戏中截取大量游戏过程中的图片
打标签有两种方式:
1:Make Sense在线打标签
2:python的一个库labelimg 使用pip install安装
使用train.py等待完成安装
cv2.namedWindow('csgo-detect', cv2.WINDOW_NORMAL)
函数原型:
void nameWindow(const string& winname,int flags = WINDOW_AUTOSIZE) ;
arg1:新建的窗口的名称。自己随便取
arg2:窗口的标识,一般默认为WINDOW_AUTOSIZE
WINDOW_AUTOSIZE 窗口大小自动适应图片大小,并且不可手动更改。(上面图1就是使用的它)
WINDOW_NORMAL 用户可以改变这个窗口大小(上面图2就是使用的它)
WINDOW_OPENGL 窗口创建的时候会支持OpenGL
cv2.resizeWindow('csgo-detect', re_x // 3, re_y // 3)
arg1:窗口名
arg2,arg3:位置
cv2.imshow('csgo-detect', img)
arg1:窗口名
arg2:图片
if cv2.waitKey(1) & 0xFF == ord('q'): cv2.destroyAllWindows() break
刚开始只知道加上cv2.waitKey之后cv2.imshow就可以显示图像了
为什么cv2.imshow之后要跟cv2.waitkey
imshow的作用是在GUI里显示一幅图像,但是它有个特点我们没有太注意,就是它的持续时间。
测试后可得
waitkey控制着imshow的持续时间,当imshow之后不跟waitkey时,相当于没有给imshow提供时间展示图像,所以只有一个空窗口一闪而过。添加了waitkey后,哪怕仅仅是cv2.waitkey(1),我们也能截取到一帧的图像。所以cv2.imshow后边是必须要跟cv2.waitkey的。
imshow源码里的注释
This function should be followed by cv::waitKey function which displays the image for specified . milliseconds. Otherwise, it won't display the image.
这个函数之后应接cv2.waitKey函数来显示指定图像。否则,它不会显示图像。
为什么要这么麻烦的设计
This function is the only method in HighGUI that can fetch and handle events, so it needs to be
这个函数是HighGUI窗口中唯一的获取和处理事件的方法,因此它必须存在
cv2.waitKey(1) & 0xFF == ord(‘q’) 的作用
在linux上使用waitkey有时会出现waitkey返回值超过了(0-255)的范围的现象,通过&0xff来解决这个现象
ord是把字符转换成ascii码
cv2.waitkey和time.sleep的区别
有人写在代码时把waitkey当sleep用过,你会发现有时waitkey并不起作用。
官方解释:The function only works if there is at least one HighGUI window created and the window is active
翻译:这个函数只有在至少一个HighGUI窗口存在的情况下才会起作用。
waitkey的延时机制是有条件的,必须在它之前创造HighGUI窗口它才会起作用。而time.sleep是无条件的延时机制。
cv2.destroyAllWindows()
关闭窗口并取消分配任何相关的内存使用。对于一个简单的程序,实际上不必调用这些函数,因为退出时操作系统会自动关闭应用程序的所有资源和窗口
使用win32gui.SetWindowPos置顶窗口
hwnd = win32gui.FindWindow(None,'windowname')
arg1:窗口类名
arg2:窗口标题
CVRECT = cv2.getWindowImageRect('windowname')
arg1:窗口标题
win32gui.SetWindowPos(hwnd,win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
函数原型
BOOL SetWindowPos(HWN hWnd,HWND hWndlnsertAfter,int X,int Y,int cx,int cy,UNIT.Flags)
功能:该函数改变一个子窗口,弹出式窗口式顶层窗口的尺寸,位置和Z序。子窗口,弹出式窗口,及顶层窗口根据它们在屏幕上出现的顺序排序、顶层窗口设置的级别最高,并且被设置为Z序的第一个窗口
arg1:窗口句柄
arg2:在z序中的位于被置位的窗口前的窗口句柄。该参数必须为一个窗口句柄,或下列值之一:
HWND_BOTTOM:将窗口置于Z序的底部。如果参数hWnd标识了一个顶层窗口,则窗口失去顶级位置,并且被置在其他窗口的底部。
HWND_DOTTOPMOST:将窗口置于所有非顶层窗口之上(即在所有顶层窗口之后)。如果窗口已经是非顶层窗口 则该 标志不起作用。
HWND_TOP:将窗口置于Z序的顶部。
HWND_TOPMOST:将窗口置于所有非顶层窗口之上。即使窗口未被激活窗口也将保持顶级位置。
x:以客户坐标指定窗口新位置的左边界。
Y:以客户坐标指定窗口新位置的顶边界。
cx:以像素指定窗口的新的宽度。
cy:以像素指定窗口的新的高度
uFlags:窗口尺寸和定位的标志。该参数可以是下列值的组合:
SWP_ASNCWINDOWPOS:如果调用进程不拥有窗口,系统会向拥有窗口的线程发出需求。这就防止调用线程在其他线程处理需求的时候发生死锁。
SWP_DEFERERASE:防止产生WM_SYNCPAINT消息。
SWP_DRAWFRAME:在窗口周围画一个边框(定义在窗口类描述中)。
SWP_FRAMECHANGED:给窗口发送WM_NCCALCSIZE消息,即使窗口尺寸没有改变也会发送该消息。如果未指定这个标志,只有在改变了窗口尺寸时才发送WM_NCCALCSIZE。
SWP_HIDEWINDOW;隐藏窗口。
SWP_NOACTIVATE:不激活窗口。如果未设置标志,则窗口被激活,并被设置到其他最高级窗口或非最高级组的顶部(根据参数hWndlnsertAfter设置)。
SWP_NOCOPYBITS:清除客户区的所有内容。如果未设置该标志,客户区的有效内容被保存并且在窗口尺寸更新和重定位后拷贝回客户区。
SWP_NOMOVE:维持当前位置(忽略X和Y参数)。
SWP_NOOWNERZORDER:不改变z序中的所有者窗口的位置。
SWP_NOREDRAW:不重画改变的内容。如果设置了这个标志,则不发生任何重画动作。适用于客户区和非客户区(包括标题栏和滚动条)和任何由于窗回移动而露出的父窗口的所有部分。如果设置了这个标志,应用程序必须明确地使窗口无效并区重画窗口的任何部分和父窗口需要重画的部分。
SWP_NOREPOSITION;与SWP_NOOWNERZORDER标志相同。
SWP_NOSENDCHANGING:防止窗口接收WM_WINDOWPOSCHANGING消息。
SWP_NOSIZE:维持当前尺寸(忽略cx和Cy参数)。
SWP_NOZORDER:维持当前Z序(忽略hWndlnsertAfter参数)。
SWP_SHOWWINDOW:显示窗口。
返回值:如果函数成功,返回值为非零;如果函数失败,返回值为零。若想获得更多错误消息,请调用GetLastError函数。
备注:如果设置了SWP_SHOWWINDOW和SWP_HIDEWINDOW标志,则窗口不能被移动和改变大小。如果使用SetWindowLoog改变了窗口的某些数据,则必须调用函数SetWindowPos来作真正的改变。使用下列的组合标志:SWP_NOMOVEISWP_NOSIZEISWP_FRAMECHANGED。
有两种方法将窗口设为最顶层窗口:一种是将参数hWndlnsertAfter设置为HWND_TOPMOST并确保没有设置SWP_NOZORDER标志;另一种是设置窗口在Z序中的位置以使其在其他存在的窗口之上。当一个窗口被置为最顶层窗口时,属于它的所有窗口均为最顶层窗口,而它的所有者的z序并不改变。
如果HWND_TOPMOST和HWND_NOTOPMOST标志均未指定,即应用程序要求窗口在激活的同时改变其在Z序中的位置时,在参数hWndinsertAfter中指定的值只有在下列条件中才使用:
在hWndlnsertAfter参数中没有设定HWND_NOTOPMOST和HWND_TOPMOST标志。
由hWnd参数标识的窗口不是激活窗口。
如果未将一个非激活窗口设定到z序的顶端,应用程序不能激活该窗口。应用程序可以无任何限制地改变被激活窗口在Z序中的位置,或激活一个窗口并将其移到最高级窗口的顶部或非最高级窗口的顶部。
如果一个顶层窗口被重定位到z序的底部(HWND_BOTTOM)或在任何非最高序的窗口之后,该窗口就不再是最顶层窗口。当一个最顶层窗口被置为非最顶级,则它的所有者窗口和所属者窗口均为非最顶层窗口。
一个非最顶端窗口可以拥有一个最顶端窗口,但反之则不可以。任何属于顶层窗口的窗口(例如一个对话框)本身就被置为顶层窗口,以确保所有被属窗口都在它们的所有者之上。
如果应用程序不在前台,但应该位于前台,就应调用SetForegroundWindow函数来设置。
Windows CE:如果这是一个可见的顶层窗口,并且未指定SWP_NOACTIVATE标志,则这个函数将激活窗口、如果这是当前的激活窗口,并且指定了SWP_NOACTIVATE或SWP_HIDEWINDOW标志,则激活另外一个可见的顶层窗口。
当在这个函数中的nFlags参数里指定了SWP_FRAMECHANGED标志时,WindowsCE重画窗口的整个非客户区,这可能会改变客户区的大小。这也是重新计算客户区的唯一途径,也是通过调用SetwindowLong函数改变窗口风格后通常使用的方法。
SetWindowPos将使WM_WINDOWPOSCHANGED消息向窗口发送,在这个消息中传递的标志与传递给函数的相同。这个函数不传递其他消息。
Windows CE 1.0不支持在hWndlnsertAber参数中的HWND_TOPMOST和HWND_NOTOPMOST常量。
Windows CE1.0不支持在fuFags参数中的SWP_DRAWFRAME和SWP_NOCOPYBITS标志。
import cv2
import numpy as np
import win32gui
import win32ui
import win32con
import win32api
def grab_screen(region=None):
hwin = win32gui.GetDesktopWindow()
if region:
left, top, x2, y2 = region
width = x2 - left + 1
height = y2 - top + 1
else:
width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
hwindc = win32gui.GetWindowDC(hwin)
srcdc = win32ui.CreateDCFromHandle(hwindc)
memdc = srcdc.CreateCompatibleDC()
bmp = win32ui.CreateBitmap()
bmp.CreateCompatibleBitmap(srcdc, width, height)
memdc.SelectObject(bmp)
memdc.BitBlt((0, 0), (width, height), srcdc, (left, top), win32con.SRCCOPY)
signedIntsArray = bmp.GetBitmapBits(True)
img = np.fromstring(signedIntsArray, dtype='uint8')
img.shape = (height, width, 4)
srcdc.DeleteDC()
memdc.DeleteDC()
win32gui.ReleaseDC(hwin, hwindc)
win32gui.DeleteObject(bmp.GetHandle())
return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
为什么不用pyautogui
因为,通过代码测试,移动10次鼠标需要花费将近一秒时间,这个时间效率有点不够用
而pynput只用了几乎0秒
使用方法
首先将鼠标封装成一个类,这样mouse就成为了一个可操作的对象
mouse = pynput.mouse.Controller()
保存着鼠标当前的位置(mouse是mouse类实例)
from pynput.mouse import Controllerlock_mode = Falsemouse = Controller()while True: x, y = mouse.position print(x, y)
这段代码实时输出你的鼠标位置。注意哈,你的鼠标位置会受到屏幕缩放设置的影响,不是你是3840x2160,你的鼠标右下角位置就一定是3840x2160.
from pynput.mouse import Controller
lock_mode = False
def on_click(x, y, button, pressed):
global lock_mode #这里声明使用全局变量
if pressed and button == button.x2: #x1,x2是鼠标侧面肩键
lock_mode = not lock_mode #模式切换
# Collect events until released
with mouse.Listener(
on_move=on_move,
on_click=on_click,
on_scroll=on_scroll) as listener:
listener.join()
# ...or, in a non-blocking fashion:
listener = mouse.Listener(
on_move=on_move,
on_click=on_click,
on_scroll=on_scroll)
listener.start()
import pynput
lock_mode = False
from pynput import mouse
with mouse.Events() as events:
while True:
it = next(events)
#当有操作且不是左键单击则读取下一个操作
while it is not None and not isinstance(it, pynput.mouse.Events.Click):
it = next(events)
if it is not None and it.button == it.button.x2 and it.pressed:
lock_mode = not lock_mode
print('lock mode','on' if lock_mode else 'off')
import pynput
def lock(aims, mouse, x, y):
#写规则,哪个离鼠标近先打哪个
mouse_pos_x, mouse_pos_y = mouse.position
dist_list = []
for det in aims:
_, x_c, y_c, _, _ = det
dist = (x * float(x_c) - mouse_pos_x) ** 2 + (y * float(y_c) - mouse_pos_y) ** 2
dist_list.append(dist)
det = aims[dist_list.index(min(dist_list))]
tag, x_center, y_center, width, height = det
tag = int(tag)
#坐标转换
x_center, width = x * float(x_center), x * float(width)
y_center, height = y * float(y_center), y * float(height)
#print(tag)
if tag == 1:#如果锁头
#print("111")
mouse.position = (x_center, y_center)
elif tag == 0:#如果锁身子
#print("222")
mouse.position = (x_center, y_center - 1 / 6 * height)
import csv
import time
def recoil_control():
f = csv.reader(open('./ammo_path/ak47.csv', encoding='utf-8'))
ak_recoil = []
for i in f:
ak_recoil.append(i)
ak_recoil[0][0] = '0'
ak_recoil = [[float(i) for i in x] for x in ak_recoil]
print(ak_recoil)
k = -1.4
mouse = pynput.mouse.Controller()
flag = 0
recoil_mode = False # mouse.button.x1
with pynput.mouse.Events() as events:
for event in events:
if isinstance(event, pynput.mouse.Events.Click):
if event.button == event.button.left:
if event.pressed:
flag = 1
else:
flag = 0
if event.button == event.button.x1 and event.pressed:
recoil_mode = not recoil_mode
print('recoil mode', 'on' if recoil_mode else 'off')
if flag and recoil_mode:
i = 0
a = next(events)
while True:
mouse.move(ak_recoil[i][0] * k, ak_recoil[i][1] * k)
i += 1
if i == 30:
break
if a is not None and isinstance(a, pynput.mouse.Events.Click) and a.button == a.button.left and not a.pressed:
break
a = next(events)
while a is not None and not isinstance(a, pynput.mouse.Events.Click):
a = next(events)
time.sleep(ak_recoil[i][2] / 1000)
flag = 0
from grabscreen import grab_screen
from cs_model import load_model
import cv2
import win32gui
import win32con
import torch
import numpy as np
from utils.general import non_max_suppression, scale_coords, xyxy2xywh
from utils.augmentations import letterbox
import pynput
from mouse_control import lock, recoil_control
from threading import Thread
from pynput.mouse import Button, Controller
device = 'cuda' if torch.cuda.is_available() else 'cpu'
half = device != 'cpu'
imgsz = 640
conf_thres = 0.4
iou_thres = 0.05
x, y = (1280, 1024) # 1280 * 1024
re_x, re_y = (2560, 1440)
model = load_model() #载入参数
stride = int(model.stride.max())
names = model.module.names if hasattr(model, 'module') else model.names
lock_mode = True #是否开启索敌模式
mouse = Controller() #鼠标对象初始化
t = Thread(target=recoil_control)
t.start()
with pynput.mouse.Events() as events:
#print('~')
while True:
it = next(events)
while it is not None and not isinstance(it, pynput.mouse.Events.Click):
it = next(events)
if it is not None and it.button == it.button.x2 and it.pressed:
lock_mode = not lock_mode
print('lock mode', 'on' if lock_mode else 'off')
img0 = grab_screen(region=(0, 0, x, y))
img0 = cv2.resize(img0, (re_x, re_y))
img = letterbox(img0, imgsz, stride=stride)[0]
img = img.transpose((2, 0, 1))[::-1]
img = np.ascontiguousarray(img)
img = torch.from_numpy(img).to(device) #对图片进行格式转换
img = img.half() if half else img.float()
img /= 255.
if len(img.shape) == 3:
img = img[None] # img = img.unsqueeze(0) 压缩数据维度
pred = model(img, augment=False, visualize=False)[0]
pred = non_max_suppression(pred, conf_thres, iou_thres, agnostic=False)
#print(pred)
aims = [] #定义一个列表
for i, det in enumerate(pred):#det存放了四个位置数据,以比例形式呈现
gn = torch.tensor(img0.shape)[[1, 0, 1, 0]]
if len(det):#根据detect.py重构
det[:, :4] = scale_coords(img.shape[2:], det[:, :4], img0.shape).round()
for *xyxy, conf, cls in reversed(det):
# bbox:(tag, x_center, y_center, x_width, y_width)
"""
0 ct_head 1 ct_body 2 t_head 3 t_body
"""
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
line = (cls, *xywh) # label format
aim = ('%g ' * len(line)).rstrip() % line
aim = aim.split(' ')
#print(aim) #aim是列表格式
aims.append(aim) #添加到列表
if len(aims):
if lock_mode:
lock(aims, mouse, x, y)#写鼠标锁人
#画矩形框
for i, det in enumerate(aims):#对于列表迭代
_, x_center, y_center, width, height = det
x_center, width = re_x * float(x_center), re_x * float(width) #捕捉区域放缩
y_center, height = re_y * float(y_center), re_y * float(height) #捕捉区域放缩
top_left = (int(x_center - width / 2.), int(y_center - height / 2.)) #左上角位置计算
bottom_right = (int(x_center + width / 2.), int(y_center + height / 2.)) #右下角位置计算
color = (0, 255, 0) # RGB 用来画框的颜色
cv2.rectangle(img0, top_left, bottom_right, color, thickness=3) #画矩形框
cv2.namedWindow('csgo-detect', cv2.WINDOW_NORMAL)
cv2.resizeWindow('csgo-detect', re_x // 3, re_y // 3)
cv2.imshow('csgo-detect', img0)
hwnd = win32gui.FindWindow(None, 'csgo-detect')
CVRECT = cv2.getWindowImageRect('csgo-detect')
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE) #一直置顶
if cv2.waitKey(1) & 0xFF == ord('q'):
cv2.destroyAllWindows()
break