slider_util.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # -*- coding: utf-8 -*-
  2. # @Author : relakkes@gmail.com
  3. # @Time : 2023/12/2 12:55
  4. # @Desc : 滑块相关的工具包
  5. import os
  6. from typing import List
  7. from urllib.parse import urlparse
  8. import cv2
  9. import httpx
  10. import numpy as np
  11. class Slide:
  12. """
  13. copy from https://blog.csdn.net/weixin_43582101 thanks for author
  14. update: relakkes
  15. """
  16. def __init__(self, gap, bg, gap_size=None, bg_size=None, out=None):
  17. """
  18. :param gap: 缺口图片链接或者url
  19. :param bg: 带缺口的图片链接或者url
  20. """
  21. self.img_dir = os.path.join(os.getcwd(), 'temp_image')
  22. if not os.path.exists(self.img_dir):
  23. os.makedirs(self.img_dir)
  24. bg_resize = bg_size if bg_size else (340, 212)
  25. gap_size = gap_size if gap_size else (68, 68)
  26. self.bg = self.check_is_img_path(bg, 'bg', resize=bg_resize)
  27. self.gap = self.check_is_img_path(gap, 'gap', resize=gap_size)
  28. self.out = out if out else os.path.join(self.img_dir, 'out.jpg')
  29. @staticmethod
  30. def check_is_img_path(img, img_type, resize):
  31. if img.startswith('http'):
  32. headers = {
  33. "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;"
  34. "q=0.8,application/signed-exchange;v=b3;q=0.9",
  35. "Accept-Encoding": "gzip, deflate, br",
  36. "Accept-Language": "zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7,ja;q=0.6",
  37. "AbstractCache-Control": "max-age=0",
  38. "Connection": "keep-alive",
  39. "Host": urlparse(img).hostname,
  40. "Upgrade-Insecure-Requests": "1",
  41. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
  42. "Chrome/91.0.4472.164 Safari/537.36",
  43. }
  44. img_res = httpx.get(img, headers=headers)
  45. if img_res.status_code == 200:
  46. img_path = f'./temp_image/{img_type}.jpg'
  47. image = np.asarray(bytearray(img_res.content), dtype="uint8")
  48. image = cv2.imdecode(image, cv2.IMREAD_COLOR)
  49. if resize:
  50. image = cv2.resize(image, dsize=resize)
  51. cv2.imwrite(img_path, image)
  52. return img_path
  53. else:
  54. raise Exception(f"保存{img_type}图片失败")
  55. else:
  56. return img
  57. @staticmethod
  58. def clear_white(img):
  59. """清除图片的空白区域,这里主要清除滑块的空白"""
  60. img = cv2.imread(img)
  61. rows, cols, channel = img.shape
  62. min_x = 255
  63. min_y = 255
  64. max_x = 0
  65. max_y = 0
  66. for x in range(1, rows):
  67. for y in range(1, cols):
  68. t = set(img[x, y])
  69. if len(t) >= 2:
  70. if x <= min_x:
  71. min_x = x
  72. elif x >= max_x:
  73. max_x = x
  74. if y <= min_y:
  75. min_y = y
  76. elif y >= max_y:
  77. max_y = y
  78. img1 = img[min_x:max_x, min_y: max_y]
  79. return img1
  80. def template_match(self, tpl, target):
  81. th, tw = tpl.shape[:2]
  82. result = cv2.matchTemplate(target, tpl, cv2.TM_CCOEFF_NORMED)
  83. # 寻找矩阵(一维数组当作向量,用Mat定义) 中最小值和最大值的位置
  84. min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
  85. tl = max_loc
  86. br = (tl[0] + tw, tl[1] + th)
  87. # 绘制矩形边框,将匹配区域标注出来
  88. # target:目标图像
  89. # tl:矩形定点
  90. # br:矩形的宽高
  91. # (0,0,255):矩形边框颜色
  92. # 1:矩形边框大小
  93. cv2.rectangle(target, tl, br, (0, 0, 255), 2)
  94. cv2.imwrite(self.out, target)
  95. return tl[0]
  96. @staticmethod
  97. def image_edge_detection(img):
  98. edges = cv2.Canny(img, 100, 200)
  99. return edges
  100. def discern(self):
  101. img1 = self.clear_white(self.gap)
  102. img1 = cv2.cvtColor(img1, cv2.COLOR_RGB2GRAY)
  103. slide = self.image_edge_detection(img1)
  104. back = cv2.imread(self.bg, cv2.COLOR_RGB2GRAY)
  105. back = self.image_edge_detection(back)
  106. slide_pic = cv2.cvtColor(slide, cv2.COLOR_GRAY2RGB)
  107. back_pic = cv2.cvtColor(back, cv2.COLOR_GRAY2RGB)
  108. x = self.template_match(slide_pic, back_pic)
  109. # 输出横坐标, 即 滑块在图片上的位置
  110. return x
  111. def get_track_simple(distance) -> List[int]:
  112. # 有的检测移动速度的 如果匀速移动会被识别出来,来个简单点的 渐进
  113. # distance为传入的总距离
  114. # 移动轨迹
  115. track: List[int] = []
  116. # 当前位移
  117. current = 0
  118. # 减速阈值
  119. mid = distance * 4 / 5
  120. # 计算间隔
  121. t = 0.2
  122. # 初速度
  123. v = 1
  124. while current < distance:
  125. if current < mid:
  126. # 加速度为2
  127. a = 4
  128. else:
  129. # 加速度为-2
  130. a = -3
  131. v0 = v
  132. # 当前速度
  133. v = v0 + a * t # type: ignore
  134. # 移动距离
  135. move = v0 * t + 1 / 2 * a * t * t
  136. # 当前位移
  137. current += move # type: ignore
  138. # 加入轨迹
  139. track.append(round(move))
  140. return track
  141. def get_tracks(distance: int, level: str = "easy") -> List[int]:
  142. if level == "easy":
  143. return get_track_simple(distance)
  144. else:
  145. from . import easing
  146. _, tricks = easing.get_tracks(distance, seconds=2, ease_func="ease_out_expo")
  147. return tricks