Files
splider/spec.md
2025-10-25 15:53:29 +08:00

5.7 KiB
Raw Blame History

需求

基于sharp图像处理库实现图片中的滑块精确识别。

滑块形状图片和标注图片

滑块抠图放在images/slider目录中这些图片是滑块的形状。注意方形滑块的某些边可能有半圆形凹陷或凸起。 标注滑块的图片放在images/target中图中标有target文字的框框是目标。注意要识别的不是target框而是target所框的滑块。

程序识别标准

images/douban-target下放置手工红框标注的图片目标在红色框框内,作为算法生成结果的比对。 如果同一张图片程序生成框和人工框匹配,通过测试,否则继续优化。 注意:不是下方绿色的滑块,是红框内的滑块。

豆瓣滑块截图

images/douban目录放置豆瓣网滑块真实截图。 完成代码后使用该目录的图片中做验证并用蓝色方框框出滑块准确位置输出到images/output目录。

思路

先识别douban-target中的红框手工标注的位置作为基准检验output中的标注与基准比较来确定是否正确识别 douban-target中的图片是手工标注所以允许output标注结果与基准有少量偏差比如10px

改进思路:

  1. 不能根据亮度,滑块的亮度不确定。
  2. 图片中滑块只有2个
  3. 滑块形状是正方形其中2个边有半圆凹陷或凸起。 最佳方法是边缘检测。滑块边框色调基本一致。

网友思路,滑块预处理

先提取一下滑块的轮廓抖音的滑块特征很明显可以不用cv2.Canny来提取边缘特征。

具体步骤如下:

  • 去除外围透明像素点(滑块外层的像素点的a值都是0)
  • 将图片转成灰度图并进行二值化操作(0和255)
  • 只保留二值化为255的像素点
  • 去除多余噪声

读取rgba格式的滑块

import cv2 input_img = cv2.imread("slide.png", cv2.IMREAD_UNCHANGED) 将透明值为0的像素点设置为纯黑色

取透明维度的值

alpha_channel = input_img[:, :, 3]

只使用rgb三个维度的值

rgb_image = input_img[:, :, :3] rgb_image[alpha_channel == 0] = [0, 0, 0] 提取白色边缘并设置成黑色,将其他像素点设置为白色

gray = cv2.cvtColor(rgb_image, cv2.COLOR_BGR2GRAY) _, thresholded = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY) white_img = np.ones_like(rgb_image) * 255 white_img[thresholded == 255] = [0, 0, 0]

去除噪声(判断某个黑色像素点周围3x3范围内有多少个黑色像素点少于阈值认为是噪声)

def count_black_neighbors_by_cv2(gray_image): if gray_image.ndim == 3: gray_image = cv2.cvtColor(gray_image, cv2.COLOR_BGR2GRAY) _, binary_image = cv2.threshold(gray_image, 240, 255, cv2.THRESH_BINARY_INV) binary_image = binary_image // 255 kernel = np.ones((3, 3), dtype=np.uint8) kernel[1, 1] = 0 black_neighbors = cv2.filter2D(binary_image, -1, kernel) # 设置边缘为0 black_neighbors[:, 0] = 0 black_neighbors[:, 109] = 0 return black_neighbors

当然也可以通过遍历来实现,这样更容易理解点

def count_black_neighbors_by_range(gray_image): # 将图像转换为灰度图 if len(gray_image.shape) == 3: gray_image = cv2.cvtColor(gray_image, cv2.COLOR_BGR2GRAY) # 二值化图像 _, binary_image = cv2.threshold(gray_image, 240, 255, cv2.THRESH_BINARY_INV) binary_image = binary_image // 255 # 创建一个与输入图像大小相同的全零数组 black_neighbors = np.zeros_like(binary_image)

# 遍历图像中的3x3邻域计算每个像素
neighbor_offsets = [(-1, -1), (-1, 0), (-1, 1),
                    (0, -1),          (0, 1),
                    (1, -1), (1, 0), (1, 1)]

# 遍历每个像素
rows, cols = binary_image.shape
for row in range(1, rows - 1):
    for col in range(1, cols - 1):
        # 当它本身不是黑色像素点的时候,就不计算
        if binary_image[row, col] != 1:
            continue
        count = 0
        for offset in neighbor_offsets:
            neighbor_row, neighbor_col = row + offset[0], col + offset[1]
            if binary_image[neighbor_row, neighbor_col] == 1:
                count += 1
        black_neighbors[row, col] = count

return black_neighbors

black_neighbors = count_black_neighbors_by_range(white_img) output = np.ones_like(rgb_image) * 255 output[black_neighbors > 4] = 0

好了,现在可以把上面看到的内容忘掉了,因为在实际识别的时候用不到(我发现不做处理比做处理识别的准确率要高很多),直接识别准确率甚至接近百分百了。

下面是识别的完整代码

import os import cv2

def get_slide_distance(bg_path, slide_path): ''' 识别滑块具体位置,返回位置比例: 位置/图片宽度 使用的时候再乘以实际图片宽度即可 ''' bg_img = cv2.imread(bg_path) sd_img = cv2.imread(slide_path) bg_gray = cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY) bg_gray = cv2.GaussianBlur(bg_gray, (5, 5), 0) bg_edge = cv2.Canny(bg_gray, 30, 100) rgb_bg_gray = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)

sd_gray = cv2.cvtColor(sd_img, cv2.COLOR_BGR2GRAY)
sd_gray = cv2.GaussianBlur(sd_gray, (5, 5), 0)
sd_edge = cv2.Canny(sd_gray, 30, 100)
rgb_sd_gray = cv2.cvtColor(sd_edge, cv2.COLOR_GRAY2RGB)
result = cv2.matchTemplate(rgb_bg_gray, rgb_sd_gray, cv2.TM_CCORR_NORMED)
_, _, _, max_loc = cv2.minMaxLoc(result)
cv2.rectangle(bg_img, (max_loc[0], max_loc[1]), (max_loc[0]+110, max_loc[1] + 110),
    (0, 255, 0), 2)
result_path = os.path.join(os.path.dirname(bg_path), "result.png")
cv2.imwrite(result_path, bg_img)
return max_loc[0]/bg_gray.shape[1]