• python 自动化学习(三) 句柄获取、模拟按键、opencv安装


    一、什么是句柄

         句柄是在操作系统中的一种标识符,相当于我们每个人的身份证一样,句柄在电脑中也是有唯一性的,我们启动的每一个程序都有自己的句柄号,表示自己的身份

        为什么要说句柄,我们如果想做自动化操作时,肯定也不想程序占用了我们整个电脑,稍微操作一下程序步骤就乱掉了,更加希望自动化程序在运行的时候能够只针对某个窗口或者某个程序进行操作,即使我们把自动化的程序放入都后台时也不影响两边的操作,这里就需要用到句柄了

    所需的包

    1. #配置清华镜像源
    2. pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
    3. pip config set global.trusted-host pypi.tuna.tsinghua.edu.cn
    4. #安装依赖库
    5. pip install pywin32

    基本使用

    1. #部分参考文档
    2. https://huaweicloud.csdn.net/63803058dacf622b8df86819.html?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromBaidu~activity-1-122498299-blog-111083068.pc_relevant_vip_default&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromBaidu~activity-1-122498299-blog-111083068.pc_relevant_vip_default&utm_relevant_index=1

    1、获取鼠标所在位置程序的句柄

    1. import time
    2. import win32api
    3. import win32gui
    4. time.sleep(2)
    5. point = win32api.GetCursorPos() #win32api.GetCursorPos 获取鼠标当前的坐标(x,y)
    6. hwnd = win32gui.WindowFromPoint(point) #查看坐标位置窗口的句柄
    7. print(hwnd) #输出句柄

    如下图,我执行了3遍分别在执行后将鼠标放在文本、桌面、idea上面,返回了句柄ID

     

    2、通过句柄获取类名

     我们每次关闭重新打开一个程序会发现句柄值变了,每次都从头找句柄就太麻烦了

    每一个程序在开发之初就存在着一个叫"类名"的概念,类名和句柄每次变更不同,它在定义后几乎是不会发生变化的,所以我们最好是先找到一个程序的类名,后续直接通过类名找到句柄,然后在通过句柄进行真正所需要的操作

     vi main.py

    1. import time
    2. import win32api
    3. import win32gui
    4. # 通过句柄获取窗口类名
    5. def get_clasname(hwnd):
    6. clasname = win32gui.GetClassName(hwnd)
    7. print('窗口类名:%s' % (clasname))
    8. return clasname
    9. time.sleep(2)
    10. point = win32api.GetCursorPos()
    11. hwnd = win32gui.WindowFromPoint(point)
    12. #查看窗口类名
    13. get_clasname(hwnd)

     可以看到上面我们获取到了文档窗口的类名,现在开始我们直接通过类名去获取句柄

    3、通过类名获取句柄

    没有找到特定的方法,我们下面大概的思路就是先把主机上所有的句柄id都拿到,通过循环把所有句柄id的类名拿出来然后做对比,对的上的id都留在列表中,所以说如果开启了多个相同程序的窗口,我们也会获取到多个句柄

    1. import time
    2. import win32api
    3. import win32gui
    4. #获取当前主机上的所有句柄id
    5. def get_all_windows():
    6. all_window_handles = []
    7. # 枚举所有窗口句柄,添加到列表中
    8. def enum_windows_proc(hwnd, param):
    9. param.append(hwnd)
    10. return True
    11. # 调用枚举窗口API
    12. win32gui.EnumWindows(enum_windows_proc, all_window_handles)
    13. return all_window_handles #返回的是一个句柄id的列表
    14. #查询传入的句柄id、类名
    15. def get_title(window_handle, class_name):
    16. #查询句柄的类名
    17. window_class = win32gui.GetClassName(window_handle)
    18. #判断窗口类名是否和指定的类名相同,如果相同则返回该窗口句柄,否则返回空值
    19. if window_class == class_name:
    20. return window_handle
    21. #遍历窗口句柄的所有子窗口
    22. def get_child_windows(parent_window_handle):
    23. child_window_handles = []
    24. def enum_windows_proc(hwnd, param):
    25. param.append(hwnd)
    26. return True
    27. #win32gui.EnumChildWindows 遍历窗口句柄的所有子窗口
    28. win32gui.EnumChildWindows(parent_window_handle, enum_windows_proc, child_window_handles)
    29. return child_window_handles
    30. # 根据标题查找窗口句柄
    31. def find_hwnd_by_title(title):
    32. all_windows = get_all_windows() #查询所有句柄
    33. matched_windows = [] #存放所有匹配类名的句柄id
    34. # 在所有窗口中查找标题匹配的窗口句柄
    35. for window_handle in all_windows:
    36. #get_title方法 检查传入句柄对应的类名和我们实际的类名是否对应
    37. window_title = get_title(window_handle, title)
    38. if window_title:
    39. matched_windows.append(window_title) #如果对应就写入列表
    40. # 如果没有匹配到,则在所有子窗口中查找标题匹配的窗口句柄
    41. if matched_windows:
    42. return matched_windows
    43. else:
    44. child_window_handles = []
    45. for parent_window_handle in all_windows:
    46. #不论子窗口是否有数据都追加到列表
    47. child_window_handles.extend(get_child_windows(parent_window_handle))
    48. for child_window_handle in child_window_handles:
    49. if get_title(child_window_handle, title):
    50. matched_windows.append(get_title(child_window_handle, title))
    51. return matched_windows
    52. if __name__ == '__main__':
    53. hwnd = find_hwnd_by_title("Edit")
    54. print(hwnd)

     可以看到我们能够直接取到相同类名下所有已经打开的窗口句柄,这样我们甚至可以做个循环加多线程,来实现一个窗口并发的效果

    二、模拟按键

    上面我们已经拿到了文本文档的一个句柄信息,通过句柄我们可以做很多事情,最常见的就是模拟鼠标和键盘的按键操作,每个操作可能都较为细小琐碎,我们定义一个class类来存放

    常见消息类型和标识

    1. #官方参考
    2. https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-lbuttondown
    消息类型作用消息标识作用
    WM_MOUSEMOVE鼠标 移动移动通用左键右键标识
    WM_RBUTTONDOWN鼠标 右键按下MK_RBUTTON左键按下
    WM_RBUTTONUP鼠标 右键释放None释放时无需标识
    WM_LBUTTONDOWN鼠标 左键按下MK_LBUTTON右键按下
    WM_LBUTTONUP鼠标 左键释放None释放时无需标识

     当按键需要被按下时,需要先声明消息类型,然后标明按键状态
      如果鼠标按键需要被释放时,可以直接通过释放按钮来释放
      如果指定消息类型是移动时,可以当作已经声明了消息类型,可以直接使用按键标识

    使用语法

    1. #在win32api下有个函数PostMessage,是用来与windows api交互的,参数如下
    2. 1、要发送消息的目标窗口的句柄
    3. 2、发送的消息类型
    4. 3、以及消息的参数
    5. win32api.PostMessage(句柄id, 消息类型, 消息标识, 具体的坐标(x,y))

    获取目标坐标

    1. #获取坐标
    2. time.sleep(3)
    3. print(win32api.GetCursorPos())

    返回

    (328, 250)

     

    鼠标按键案例

          下面定义了一个类,先去接受我们上面获取到的句柄id,在使用鼠标按键的时候调用win32api.PostMessage函数 去发送给句柄所在的窗口按键信息

       在左右键按下的时候才需要定义标识,比如模拟左键时会使用WM_LBUTTONDOWN和MK_LBUTTON  ,而松开时使用WM_LBUTTONUP和None

       变量pos 是只鼠标按键的坐标,需要通过win32api.MAKELONG 转换数据类型后才能调用

    1. #声明鼠标操作的类
    2. class WinMouse(object):
    3. #初始化函数,接受传入的句柄id
    4. def __init__(self, handle_num: int):
    5. self.handle = handle_num
    6. #鼠标左键按下
    7. def left_button_down(self, pos):
    8. win32api.PostMessage(self.handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, pos)
    9. #鼠标左键释放
    10. def left_button_up(self, pos):
    11. win32api.PostMessage(self.handle, win32con.WM_LBUTTONUP, None, pos)
    12. if __name__ == '__main__':
    13. hwnd = find_hwnd_by_title("Edit") #通过类名获取句柄
    14. bd = WinMouse(hwnd[0]) #实例化WinMouse 类,传入句柄值
    15. pos = win32api.MAKELONG(328, 250) #将正常的x,y坐标值转换为特定的数据结构,
    16. #给win32api.PostMessage调用
    17. #按下、等待1s、松开
    18. bd.left_button_down(pos)
    19. time.sleep(1)
    20. bd.left_button_up(pos)

    可以看到在下图中,我们运行程序后,不论文本文档是否在前台还是后台,哪怕被遮挡住后也会照常进行鼠标点击(数字太多看不清,大致就是我把鼠标放到末尾,程序在我上面取坐标的地方点一下左键)

    其他按键补全

    1. #按下鼠标左键并移动
    2. def mouse_move(self, pos):
    3. win32api.PostMessage(self.handle, win32con.WM_MOUSEMOVE, win32con.MK_LBUTTON, pos)
    4. #按下鼠标右键并移动
    5. def right_button_move(self, pos):
    6. win32api.PostMessage(self.handle, win32con.WM_MOUSEMOVE, win32con.MK_RBUTTON, pos)
    7. #指定坐标按下右键
    8. def right_button_down(self, pos):
    9. win32api.PostMessage(self.handle, win32con.WM_RBUTTONDOWN, win32con.MK_RBUTTON, pos)
    10. #右键释放
    11. def right_button_up(self, pos):
    12. win32api.PostMessage(self.handle, win32con.WM_RBUTTONUP, None, pos)
    13. #模拟左键双击
    14. def left_double_click(self, x_pos:int, y_pos:int, click=2, wait=0.4):
    15. wait = wait / click #click 表示点击次数,wait是的等待时间,意思是双击的间隔
    16. point = win32api.MAKELONG(x_pos, y_pos)
    17. for i in range(click):
    18. self.left_button_down(point)
    19. time.sleep(wait)
    20. self.left_button_up(point)
    21. #右键双击
    22. def right_doubleClick(self, x, y, click=2, wait=0.4):
    23. wait = wait / click
    24. pos = win32api.MAKELONG(x, y)
    25. for i in range(click):
    26. self.right_button_down(pos)
    27. time.sleep(wait)
    28. self.right_button_up(pos)

    按键组合函数

    上面用一个按左键都要好几行,我们这里在给封装一下

    1. #让他可以直接接收x,y坐标,wait是松开按键的间隔,一般默认即可
    2. #左键单击
    3. def left_click(self, x_pos:int, y_pos:int, wait=0.2):
    4. point = win32api.MAKELONG(x_pos, y_pos)
    5. self.left_button_down(point)
    6. time.sleep(wait)
    7. self.left_button_up(point)
    8. #右键单击
    9. def right_click(self, x_pos:int, y_pos:int, wait=0.2):
    10. point = win32api.MAKELONG(x_pos, y_pos)
    11. self.right_button_down(point)
    12. time.sleep(wait)
    13. self.right_button_up(point)
    14. #模拟左键双击
    15. def left_double_click(self, x_pos:int, y_pos:int, click=2, wait=0.4):
    16. wait = wait / click #click 表示点击次数,wait是的等待时间,意思是双击的间隔
    17. point = win32api.MAKELONG(x_pos, y_pos)
    18. for i in range(click):
    19. self.left_button_down(point)
    20. time.sleep(wait)
    21. self.left_button_up(point)
    22. #右键双击
    23. def right_doubleClick(self, x, y, click=2, wait=0.4):
    24. wait = wait / click
    25. pos = win32api.MAKELONG(x, y)
    26. for i in range(click):
    27. self.right_button_down(pos)
    28. time.sleep(wait)
    29. self.right_button_up(pos)

    鼠标滑动拖拽

    添加偏移值

     vi main.py

    1. #计算鼠标从起始点到目标点的偏移过程
    2. def getPointOnLine(start_x, start_y, end_x, end_y, ratio):
    3. x = ((end_x - start_x) * ratio) + start_x
    4. y = ((end_y - start_y) * ratio) + start_y
    5. return int(round(x)), int(round(y))
    6. class WinMouse(object):
    7. def __init__(self, handle_num: int, num_of_steps=80): #添加num_of_steps=80
    8. self.handle = handle_num
    9. self.num_of_steps = num_of_steps #添加偏移值

    添加左右键拖动方法

    1. #模拟点击并拖拽目标,接受两对坐标值
    2. def left_click_move(self, x1:int, y1:int, x2:int, y2:int, wait=2):
    3. point1 = win32api.MAKELONG(x1, y1)
    4. self.left_button_down(point1) #起始点按下鼠标左键
    5. #获取我们在init初始化时定义的偏移值
    6. steps = self.num_of_steps
    7. #调用我们上面的方法返回具体,循环0-80的值
    8. #你看这里的循环值是80,也就说会做80次循环操作
    9. #我们传入了起始坐标和目标坐标,而i / steps就相当于起始到结束的偏移位置
    10. #可以理解为从左上角到右下角的点
    11. points = [getPointOnLine(x1, y1, x2, y2, i / steps) for i in range(steps)]
    12. points.append((x2, y2))
    13. wait_time = wait / steps
    14. unique_points = list(set(points))
    15. unique_points.sort(key=points.index)
    16. for point in unique_points:
    17. x, y = point
    18. point = win32api.MAKELONG(x, y)
    19. self.mouse_move(point)
    20. time.sleep(wait_time)
    21. self.left_button_up(point)
    22. #右键单击并滑动批量勾选(与上方函数同理)
    23. def right_click_move(self, start_x, start_y, end_x, end_y, wait=2):
    24. pos = win32api.MAKELONG(start_x, start_y)
    25. self.right_button_down(pos)
    26. steps = self.num_of_steps
    27. points = [getPointOnLine(start_x, start_y, end_x, end_y, i / steps) for i in range(steps)]
    28. points.append((end_x, end_y))
    29. time_per_step = wait / steps
    30. distinct_points = list(set(points))
    31. distinct_points.sort(key=points.index)
    32. for point in distinct_points:
    33. x, y = point
    34. pos = win32api.MAKELONG(x, y)
    35. self.right_button_move(pos)
    36. time.sleep(time_per_step)
    37. self.right_button_up(pos)

    演示

        bd.left_click_move(109,180,232,341)

    全量代码

    1. import time
    2. import win32api
    3. import win32con
    4. import win32gui
    5. #---------------------------------------------------句柄配置的分割线
    6. #获取当前主机上的所有句柄id
    7. def get_all_windows():
    8. all_window_handles = []
    9. # 枚举所有窗口句柄,添加到列表中
    10. def enum_windows_proc(hwnd, param):
    11. param.append(hwnd)
    12. return True
    13. # 调用枚举窗口API
    14. win32gui.EnumWindows(enum_windows_proc, all_window_handles)
    15. return all_window_handles #返回的是一个句柄id的列表
    16. #查询传入的句柄id、类名
    17. def get_title(window_handle, class_name):
    18. #查询句柄的类名
    19. window_class = win32gui.GetClassName(window_handle)
    20. #判断窗口类名是否和指定的类名相同,如果相同则返回该窗口句柄,否则返回空值
    21. if window_class == class_name:
    22. return window_handle
    23. #遍历窗口句柄的所有子窗口
    24. def get_child_windows(parent_window_handle):
    25. child_window_handles = []
    26. def enum_windows_proc(hwnd, param):
    27. param.append(hwnd)
    28. return True
    29. #win32gui.EnumChildWindows 遍历窗口句柄的所有子窗口
    30. win32gui.EnumChildWindows(parent_window_handle, enum_windows_proc, child_window_handles)
    31. return child_window_handles
    32. # 根据标题查找窗口句柄
    33. def find_hwnd_by_title(title):
    34. all_windows = get_all_windows() #查询所有句柄
    35. matched_windows = [] #存放所有匹配类名的句柄id
    36. # 在所有窗口中查找标题匹配的窗口句柄
    37. for window_handle in all_windows:
    38. #get_title方法 检查传入句柄对应的类名和我们实际的类名是否对应
    39. window_title = get_title(window_handle, title)
    40. if window_title:
    41. matched_windows.append(window_title) #如果对应就写入列表
    42. # 如果没有匹配到,则在所有子窗口中查找标题匹配的窗口句柄
    43. if matched_windows:
    44. return matched_windows
    45. else:
    46. child_window_handles = []
    47. for parent_window_handle in all_windows:
    48. #不论子窗口是否有数据都追加到列表
    49. child_window_handles.extend(get_child_windows(parent_window_handle))
    50. for child_window_handle in child_window_handles:
    51. if get_title(child_window_handle, title):
    52. matched_windows.append(get_title(child_window_handle, title))
    53. return matched_windows
    54. #-----------------------------------------------------句柄配置的分割线
    55. def getPointOnLine(start_x, start_y, end_x, end_y, ratio):
    56. x = ((end_x - start_x) * ratio) + start_x
    57. y = ((end_y - start_y) * ratio) + start_y
    58. return int(round(x)), int(round(y))
    59. #声明鼠标操作的类
    60. class WinMouse(object):
    61. #初始化函数,接受传入的句柄id
    62. def __init__(self, handle_num: int, num_of_steps=80):
    63. self.handle = handle_num
    64. self.num_of_steps = num_of_steps
    65. #鼠标左键按下
    66. def left_button_down(self, pos):
    67. win32api.PostMessage(self.handle, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, pos)
    68. #鼠标左键释放
    69. def left_button_up(self, pos):
    70. win32api.PostMessage(self.handle, win32con.WM_LBUTTONUP, None, pos)
    71. #按下鼠标左键并移动
    72. def mouse_move(self, pos):
    73. win32api.PostMessage(self.handle, win32con.WM_MOUSEMOVE, win32con.MK_LBUTTON, pos)
    74. #按下鼠标右键并移动
    75. def right_button_move(self, pos):
    76. win32api.PostMessage(self.handle, win32con.WM_MOUSEMOVE, win32con.MK_RBUTTON, pos)
    77. #指定坐标按下右键
    78. def right_button_down(self, pos):
    79. win32api.PostMessage(self.handle, win32con.WM_RBUTTONDOWN, win32con.MK_RBUTTON, pos)
    80. #右键释放
    81. def right_button_up(self, pos):
    82. win32api.PostMessage(self.handle, win32con.WM_RBUTTONUP, None, pos)
    83. #--------------------------------------------------------封装按键方法的分割线
    84. #让他可以直接接收x,y坐标,wait是松开按键的间隔,一般默认即可
    85. #左键单击
    86. def left_click(self, x_pos:int, y_pos:int, wait=0.2):
    87. point = win32api.MAKELONG(x_pos, y_pos)
    88. self.left_button_down(point)
    89. time.sleep(wait)
    90. self.left_button_up(point)
    91. #右键单击
    92. def right_click(self, x_pos:int, y_pos:int, wait=0.2):
    93. point = win32api.MAKELONG(x_pos, y_pos)
    94. self.right_button_down(point)
    95. time.sleep(wait)
    96. self.right_button_up(point)
    97. #模拟左键双击
    98. def left_double_click(self, x_pos:int, y_pos:int, click=2, wait=0.4):
    99. wait = wait / click #click 表示点击次数,wait是的等待时间,意思是双击的间隔
    100. point = win32api.MAKELONG(x_pos, y_pos)
    101. for i in range(click):
    102. self.left_button_down(point)
    103. time.sleep(wait)
    104. self.left_button_up(point)
    105. #右键双击
    106. def right_doubleClick(self, x, y, click=2, wait=0.4):
    107. wait = wait / click
    108. pos = win32api.MAKELONG(x, y)
    109. for i in range(click):
    110. self.right_button_down(pos)
    111. time.sleep(wait)
    112. self.right_button_up(pos)
    113. #模拟点击并拖拽目标,接受两对坐标值
    114. #模拟点击并拖拽目标,接受两对坐标值
    115. def left_click_move(self, x1:int, y1:int, x2:int, y2:int, wait=2):
    116. point1 = win32api.MAKELONG(x1, y1)
    117. self.left_button_down(point1) #起始点按下鼠标左键
    118. #获取我们在init初始化时定义的偏移值
    119. steps = self.num_of_steps
    120. #调用我们上面的方法返回具体,循环0-80的值
    121. #你看这里的循环值是80,也就说会做80次循环操作
    122. #我们传入了起始坐标和目标坐标,而i / steps就相当于起始到结束的偏移位置
    123. #可以理解为从左上角到右下角的点
    124. points = [getPointOnLine(x1, y1, x2, y2, i / steps) for i in range(steps)]
    125. points.append((x2, y2))
    126. wait_time = wait / steps
    127. unique_points = list(set(points))
    128. unique_points.sort(key=points.index)
    129. for point in unique_points:
    130. x, y = point
    131. point = win32api.MAKELONG(x, y)
    132. self.mouse_move(point)
    133. time.sleep(wait_time)
    134. self.left_button_up(point)
    135. #右键单击并滑动批量勾选(与上方函数同理)
    136. def right_click_move(self, start_x, start_y, end_x, end_y, wait=2):
    137. pos = win32api.MAKELONG(start_x, start_y)
    138. self.right_button_down(pos)
    139. steps = self.num_of_steps
    140. points = [getPointOnLine(start_x, start_y, end_x, end_y, i / steps) for i in range(steps)]
    141. points.append((end_x, end_y))
    142. time_per_step = wait / steps
    143. distinct_points = list(set(points))
    144. distinct_points.sort(key=points.index)
    145. for point in distinct_points:
    146. x, y = point
    147. pos = win32api.MAKELONG(x, y)
    148. self.right_button_move(pos)
    149. time.sleep(time_per_step)
    150. self.right_button_up(pos)
    151. if __name__ == '__main__':
    152. hwnd = find_hwnd_by_title("Edit") #通过类名获取句柄
    153. bd = WinMouse(hwnd[0]) #实例化WinMouse 类,传入句柄值
    154. bd.left_click_move(109,180,232,341)

    三、准备opencv环境

    我们上面简单的实现了鼠标后台模拟操作,但是存在一个问题,坐标都是我们固定好的,而在实际使用中,我们的要点击的坐标一定是多变的,我们更希望当我们要做自动化操作的时候,给他一个图片,然后就能拿到对应点击位置的一个坐标

    安装模块

    1. pip install opencv-python
    2. pip install pillow
    3. pip install opencv-contrib-python
    4. pip install numpy
    5. pip install PIL

    我这边在安装上opencv-python 后调用cv2下任意方法都提示报黄,没有代码提示,下面列出解决方法

    1. (venv) PS C:\Users\Administrator\IdeaProjects\test> pip install opencv-python
    2. Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
    3. Requirement already satisfied: opencv-python in c:\users\administrator\ideaprojects\test\venv\lib\site-packages (4.7.0.72)
    4. Requirement already satisfied: numpy>=1.21.2 in c:\users\administrator\ideaprojects\test\venv\lib\site-packages (from opencv-python) (1.24.3)

    我们在安装成功opencv-python模块后会返回一个安装路径,登录这个路径,进入cv2的目录下,将cv2.pyd 文件放到下面的路径下,重启编辑器即可

    c:\users\administrator\ideaprojects\test\venv\lib\site-packages

     

    1. pip install opencv-contrib-python
    2. 后来发现 单独装这个,不装opencv也可以用,还剩的复制文件,先看看,没问题就改

    测试语句

    1. import cv2
    2. # 加载一张图片
    3. img = cv2.imread('11.png', 1) #脚本文件旁边自行准备一个图片
    4. # 显示图片
    5. cv2.imshow('image', img)
    6. cv2.waitKey(0)
    7. cv2.destroyAllWindows()

    1、添加截图函数

    首先,我们想要获取想要获取小图所在的坐标,要先拿到大图的信息,这里我们以桌面为案例,通过获取桌面的句柄信息,然后进行截图

    vi main.py

    1. def capture_window(hwnd,img):
    2. # 获取句柄窗口位置
    3. left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    4. width = right - left
    5. height = bottom - top
    6. # 获取窗口DC
    7. hwndDC = win32gui.GetWindowDC(hwnd)
    8. mfcDC = win32ui.CreateDCFromHandle(hwndDC)
    9. saveDC = mfcDC.CreateCompatibleDC()
    10. # 创建位图对象
    11. saveBitMap = win32ui.CreateBitmap()
    12. saveBitMap.CreateCompatibleBitmap(mfcDC, width, height)
    13. saveDC.SelectObject(saveBitMap)
    14. # 截取窗口内容
    15. saveDC.BitBlt((0, 0), (width, height), mfcDC, (0, 0), win32con.SRCCOPY)
    16. # 保存位图到内存中
    17. bmpinfo = saveBitMap.GetInfo()
    18. bmpstr = saveBitMap.GetBitmapBits(True)
    19. # 将位图数据转换为OpenCV格式
    20. image = np.frombuffer(bmpstr, dtype='uint8')
    21. image.shape = (height, width, 4)
    22. # 释放资源
    23. win32gui.DeleteObject(saveBitMap.GetHandle())
    24. saveDC.DeleteDC()
    25. mfcDC.DeleteDC()
    26. win32gui.ReleaseDC(hwnd, hwndDC)
    27. #文件保存
    28. # 转换为BGR格式
    29. bgr_image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
    30. # 保存图像到文件
    31. cv2.imwrite(img, bgr_image)
    32. return cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)

    vi main.py

    1. if __name__ == '__main__':
    2. hwnd = find_hwnd_by_title("SysListView32")
    3. #调用截取特定句柄窗口截图
    4. capture_window(hwnd[0])

    不知道是不是我服务器配置太低了,图片生成查不到20s左右,不过没关系,这里出图只是看一眼,后面不用

    2、对比小图片在句柄窗口坐标

    这里我们截一张句柄窗口内的截图,范围越小,误差越小  这里命名为111.png文件,那个大图文件不用管他

    1. def EtachImage(mix_img,large_image):
    2. # 选择匹配方法
    3. method = cv2.TM_SQDIFF_NORMED
    4. # method = cv2.TM_CCOEFF_NORMED
    5. # 读取小图
    6. small_image = cv2.imread(mix_img)
    7. # 使用模板匹配
    8. result = cv2.matchTemplate(small_image, large_image, method)
    9. mn, _, mnLoc, _ = cv2.minMaxLoc(result)
    10. # 提取最佳匹配的坐标
    11. MPx, MPy = mnLoc
    12. #print("矩形框左上角坐标:({}, {})".format(MPx, MPy))
    13. # 获取小图尺寸
    14. trows, tcols = small_image.shape[:2]
    15. # 在大图上绘制矩形框
    16. cv2.rectangle(large_image, (MPx, MPy), (MPx + tcols, MPy + trows), (0, 0, 255), 2)
    17. #显示大图,没居中之前的矩形
    18. # cv2.imshow('output', large_image)
    19. # cv2.waitKey(0)
    20. # cv2.destroyAllWindows()
    21. # 计算矩形框中心点坐标
    22. center_x = int(MPx + tcols / 2)
    23. center_y = int(MPy + trows / 2)
    24. # 在大图上绘制中心点
    25. cv2.circle(large_image, (center_x, center_y), 5, (255, 0, 0), -1)
    26. # 显示带有中心点的大图
    27. cv2.imshow('output', large_image)
    28. cv2.waitKey(0)
    29. cv2.destroyAllWindows()
    30. # 返回中心点坐标
    31. return center_x, center_y

    vi main.py

    1. if __name__ == '__main__':
    2. hwnd = find_hwnd_by_title("SysListView32")
    3. # 获取大图截图
    4. large_image = capture_window(hwnd[0],"asdf.png")
    5. # 匹配小图所处的大图坐标
    6. x, y = EtachImage('111.png',large_image)
    7. print(x, y)

    3、验证获取的坐标是否准确

    这里为了方便查看,将显示图片的那个注释掉了

    1. def EtachImage(mix_img,large_image):
    2. 。。。
    3. # 显示带有中心点的大图
    4. # cv2.imshow('output', large_image)
    5. # cv2.waitKey(0)
    6. # cv2.destroyAllWindows()
    7. # 返回中心点坐标
    8. return center_x, center_y

    vi test.py

    1. import cv2
    2. import pyautogui
    3. # 将鼠标移动到矩形框中心点坐标处
    4. pyautogui.moveTo(425, 161, duration=1)
    5. # 输出提示信息
    6. print("鼠标已移动到矩形框中心点坐标处")
    7. # 等待一段时间
    8. cv2.waitKey(3000) # 暂停3秒,方便观察

    ok 坐标能拿到了,剩下的就靠发挥想象力喽

     (我碰到的一个bug,我是win server主机,如果把图标放到最下面哪一行,就会出现无法识别了,看截图也是截不到的区域,感觉是分辨率问题,不知道pc端有没有相似问题,注意一下)

  • 相关阅读:
    学习pytorch15 优化器
    力扣 1668. 最大重复子字符串
    从多表连接视图对比人大金仓和Oracle
    灰色和测试环境打包串台
    java spring cloud 企业电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展
    Python制作炫酷的个人足迹地图
    高性能设计要点
    NewStarCTF2023week2-include 0。0
    Seaborn绘制热力图的子图
    2022-08-01
  • 原文地址:https://blog.csdn.net/qq_42883074/article/details/130825811