5.7 KiB
需求
基于sharp图像处理库,实现图片中的滑块精确识别。
滑块形状图片和标注图片
滑块抠图放在images/slider目录中,这些图片是滑块的形状。注意:方形滑块的某些边可能有半圆形凹陷或凸起。 标注滑块的图片放在images/target中,图中标有target文字的框框是目标。注意:要识别的不是target框,而是target所框的滑块。
程序识别标准
images/douban-target下放置手工红框标注的图片,目标在红色框框内,作为算法生成结果的比对。 如果同一张图片程序生成框和人工框匹配,通过测试,否则继续优化。 注意:不是下方绿色的滑块,是红框内的滑块。
豆瓣滑块截图
images/douban目录放置豆瓣网滑块真实截图。 完成代码后,使用该目录的图片中做验证,并用蓝色方框框出滑块准确位置,输出到images/output目录。
思路
先识别douban-target中的红框手工标注的位置,作为基准,检验output中的标注与基准比较,来确定是否正确识别 douban-target中的图片是手工标注,所以允许output标注结果与基准有少量偏差,比如10px
改进思路:
- 不能根据亮度,滑块的亮度不确定。
- 图片中滑块只有2个
- 滑块形状是正方形,其中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]