mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-15 17:30:27 +08:00
* fix: 修复 ImageCropper 循环异常退出的问题 * feat: 允许 ADB 为空 * fix: 修复 input 为空时的异常 * chore: Auto update by pre-commit hooks [skip changelog] * chore: 规范变量命名 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
197 lines
5.8 KiB
Python
197 lines
5.8 KiB
Python
import cv2
|
||
import numpy as np
|
||
|
||
|
||
def kmeansClusterColors(
|
||
img,
|
||
method: int = -1,
|
||
K: int = 3,
|
||
criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2),
|
||
) -> list:
|
||
"""
|
||
将图片颜色类聚
|
||
|
||
Args:
|
||
img:
|
||
图片
|
||
method: int = -1
|
||
颜色匹配方式。即 cv::ColorConversionCodes。可选,默认 4 (RGB)。
|
||
常用值:4 (RGB, 3 通道), 40 (HSV, 3 通道), 6 (GRAY, 1 通道)。
|
||
默认不进行转换。
|
||
K: int = 3
|
||
颜色聚类个数
|
||
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
|
||
K-means 参数,迭代停止的模式选择
|
||
Return:
|
||
[( center color, array([color, ...]), ... )]
|
||
"""
|
||
if method >= 0:
|
||
img = cv2.cvtColor(img, method)
|
||
# 将图像数据转换为一维数组,保留颜色通道
|
||
pixels = img.reshape((-1, img.shape[-1]))
|
||
# 聚类
|
||
_, labels, centers = cv2.kmeans(
|
||
pixels.astype(np.float32),
|
||
K,
|
||
None,
|
||
criteria,
|
||
10,
|
||
cv2.KMEANS_RANDOM_CENTERS,
|
||
)
|
||
# 将颜色值转换为原来的颜色通道类型
|
||
centers = centers.astype(img.dtype)
|
||
# __show(centers[labels.reshape(img.shape[:-1])])
|
||
# 返回结果
|
||
ret = []
|
||
for i, center in enumerate(centers):
|
||
colors = pixels[(labels == i).flatten()]
|
||
ret.append((center, colors))
|
||
return ret
|
||
|
||
|
||
def getCount(img, lower, upper, connected: bool, method: int = -1) -> int:
|
||
"""
|
||
获取匹配成功的数量
|
||
|
||
Args:
|
||
img:
|
||
图片
|
||
lower:
|
||
颜色下限值
|
||
upper:
|
||
颜色上限值
|
||
connected: bool
|
||
是否是相连的点才会被计数
|
||
method: int = -1
|
||
颜色匹配方式。即 cv::ColorConversionCodes。可选,默认 4 (RGB)。
|
||
常用值:4 (RGB, 3 通道), 40 (HSV, 3 通道), 6 (GRAY, 1 通道)。
|
||
默认不进行转换。
|
||
Return:
|
||
int
|
||
"""
|
||
# https://github.com/MaaXYZ/MaaFramework/blob/main/source/MaaFramework/Vision/ColorMatcher.cpp
|
||
# ColorMatcher::color_match
|
||
if method >= 0:
|
||
img = cv2.cvtColor(img, method)
|
||
bin = cv2.inRange(img, np.array(lower, img.dtype), np.array(upper, img.dtype))
|
||
# __show(img)
|
||
# __show(bin)
|
||
if connected:
|
||
return __count_non_zero_with_connected(bin)
|
||
else:
|
||
return cv2.countNonZero(bin)
|
||
|
||
|
||
def __count_non_zero_with_connected(bin):
|
||
number, labels, stats, centroids = cv2.connectedComponentsWithStats(
|
||
bin, connectivity=8, ltype=cv2.CV_16U
|
||
)
|
||
count = 0
|
||
for i in range(1, number):
|
||
x = stats[i][cv2.CC_STAT_LEFT]
|
||
y = stats[i][cv2.CC_STAT_TOP]
|
||
width = stats[i][cv2.CC_STAT_WIDTH]
|
||
height = stats[i][cv2.CC_STAT_HEIGHT]
|
||
count = max(
|
||
count,
|
||
cv2.countNonZero(bin[int(y) : int(y + height), int(x) : int(x + width)]),
|
||
)
|
||
return count
|
||
|
||
|
||
def showClusterColors(cluster_colors):
|
||
"""debug用"""
|
||
for center, colors in cluster_colors:
|
||
# 创建一个用于显示颜色的图像
|
||
img = np.zeros((100, len(colors), 3), dtype=np.uint8)
|
||
# 填充颜色
|
||
img[:50, :] = center
|
||
for i, color in enumerate(colors):
|
||
img[50:, i] = color
|
||
# 显示图像
|
||
__show(img)
|
||
|
||
|
||
def __show(img):
|
||
"""debug用"""
|
||
cv2.imshow("debug show", img)
|
||
cv2.waitKey(0)
|
||
cv2.destroyAllWindows()
|
||
|
||
|
||
def __getBoxPlotValues(img, threshold: float = 1.5):
|
||
"""return: minimum, lower quartile, median, upper quartile, maximum"""
|
||
channel = img.shape[-1]
|
||
colors = img.reshape((-1, channel))
|
||
ret = ([], [], [], [], [])
|
||
for i in range(channel):
|
||
ccs = colors[:, i]
|
||
# 计算各项数据
|
||
q1 = np.percentile(ccs, 25)
|
||
q2 = np.percentile(ccs, 50)
|
||
q3 = np.percentile(ccs, 75)
|
||
# 计算上下边界
|
||
iqr = q3 - q1
|
||
lower_bound = q1 - iqr * threshold
|
||
upper_bound = q3 + iqr * threshold
|
||
# 排除异常值
|
||
q0 = max(ccs.min(), lower_bound)
|
||
q4 = min(ccs.max(), upper_bound)
|
||
# 返回结果
|
||
ret[0].append(q0.astype(img.dtype))
|
||
ret[1].append(q1.astype(img.dtype))
|
||
ret[2].append(q2.astype(img.dtype))
|
||
ret[3].append(q3.astype(img.dtype))
|
||
ret[4].append(q4.astype(img.dtype))
|
||
return ret
|
||
|
||
|
||
# 简易方法
|
||
def Simple(cluster_colors) -> list[tuple[list[int]]]:
|
||
"""
|
||
基于四分位数的一种简易匹配方法
|
||
|
||
Args:
|
||
cluster_colors
|
||
方法 clusterColors() 返回的结果
|
||
|
||
Return:
|
||
[(center, lower, upper), ...]
|
||
"""
|
||
ret = []
|
||
for center, colors in cluster_colors:
|
||
_, lower, _, upper, _ = __getBoxPlotValues(colors)
|
||
ret.append((list(center), list(lower), list(upper)))
|
||
return ret
|
||
|
||
|
||
def RGBDistance(cluster_colors, threshold: int = 50) -> list[tuple[list[int]]]:
|
||
"""
|
||
基于 RGB 通道的一种加权欧式距离匹配方法
|
||
|
||
Args:
|
||
cluster_colors
|
||
方法 clusterColors() 返回的结果
|
||
threshold: int = 50
|
||
阈值 0 - 765 (255 * 3)
|
||
|
||
Return:
|
||
[(center, lower, upper), ...]
|
||
"""
|
||
# https://www.compuphase.com/cmetric.htm
|
||
ret = []
|
||
for center, colors in cluster_colors:
|
||
center = center.astype(np.int32)
|
||
colors = colors.astype(np.int32)
|
||
rmean = ((colors[:, 0] + center[0]) / 2).astype(np.int32)
|
||
r = colors[:, 0] - center[0]
|
||
g = colors[:, 1] - center[1]
|
||
b = colors[:, 2] - center[2]
|
||
distances = np.sqrt(
|
||
(((512 + rmean) * r**2) >> 8) + 4 * g**2 + (((767 - rmean) * b**2) >> 8)
|
||
)
|
||
matched = colors[distances < threshold]
|
||
lower, _, _, _, upper = __getBoxPlotValues(matched)
|
||
ret.append((list(center), list(lower), list(upper)))
|
||
return ret
|