OpenCV关键点检测特定颜色目标失效问题排查
问题描述
- 实现目标:基于关键点检测实现游戏内文本识别功能
- 场景特征:检测区域背景为动态缓慢移动的星空效果,位移幅度极小,肉眼几乎难以分辨
- 检测效果差异:
- 待识别文本为白色时检测效果良好:

- 文本随机变为紫色(颜色变化触发时机无规律)时检测完全失效:

- 待识别文本为白色时检测效果良好:
- 前置条件:待检测目标与检测所用模板图像完全一致,均为游戏内同一位置直接截取的文本截图,检测时也限定在原截图对应的屏幕区域内运行。
- 实现参考:代码基于OpenCV官方特征检测教程、FLANN特征匹配教程编写,官方文档对相关实现的原理说明较为简略。
核心咨询问题:该检测失效现象是关键点检测算法的固有局限,还是可以通过调整方法、参数实现紫色文本场景下的关键点正常检测?
复现代码
import cv2 as cv import win32gui, win32con, win32ui import numpy as np import glob def get_haystack_image(): w, h = 1920, 1080 hwnd = None wDC = win32gui.GetWindowDC(hwnd) dcObj = win32ui.CreateDCFromHandle(wDC) cDC = dcObj.CreateCompatibleDC() dataBitMap = win32ui.CreateBitmap() dataBitMap.CreateCompatibleBitmap(dcObj, w, h) cDC.SelectObject(dataBitMap) cDC.BitBlt((0, 0), (w, h), dcObj, (0, 0), win32con.SRCCOPY) signedIntsArray = dataBitMap.GetBitmapBits(True) img = np.frombuffer(signedIntsArray, dtype='uint8') img.shape = (h, w, 4) dcObj.DeleteDC() cDC.DeleteDC() win32gui.ReleaseDC(hwnd, wDC) win32gui.DeleteObject(dataBitMap.GetHandle()) img = img[...,:3] img = np.ascontiguousarray(img) return img def loadImages(directory): # Intialise empty array image_list = [] # Add images to array for i in directory: img = cv.imread(i, cv.IMREAD_UNCHANGED) image_list.append((img, i)) return image_list def preProcessNeedle(image_list): needle_kp1_desc = [] for i in image_list: img = i[0] orb = cv.ORB_create(edgeThreshold=0, patchSize=32) keypoint_needle, descriptors_needle = orb.detectAndCompute(img, None) needle_kp1_desc.append((keypoint_needle, descriptors_needle, img)) return needle_kp1_desc def match_keypoints(descriptors_needle, keypoint_haystack, min_match_count): orbHaystack = cv.ORB_create(edgeThreshold=0, patchSize=32, nfeatures=3000) keypoints_haystack, descriptors_haystack = orbHaystack.detectAndCompute(keypoint_haystack, None) FLANN_INDEX_LSH = 6 index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=6, key_size=12, multi_probe_level=1) search_params = dict(checks=50) try: flann = cv.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(descriptors_needle, descriptors_haystack, k=2) except cv.error: return None, None, [], [] good = [] points = [] for pair in matches: if len(pair) == 2: if pair[0].distance < 0.7*pair[1].distance: good.append(pair[0]) if len(good) > min_match_count: for match in good: points.append(keypoints_haystack[match.trainIdx].pt) return keypoints_haystack, good, points def shipDetection(needle_kp1_desc): res = False # Object Detection for i, img in enumerate(needle_kp1_desc): kp1 = img[0] descriptors_needle = img[1] needle_img = img[2] # get an updated image of the screen & crop it keypoint_haystack = get_haystack_image() keypoint_haystack = keypoint_haystack[40:110, 850:1000] kp2, matches, match_points, ship_avoided = match_keypoints(kp1, descriptors_needle, keypoint_haystack, min_match_count=40) # display the matches match_image = cv.drawMatches(needle_img, kp1, keypoint_haystack, kp2, matches, None) cv.imshow('Keypoint Search', match_image) cv.moveWindow("Keypoint Search",1940,30) cv.waitKey(1) if match_points: # removed code as irrelevant to detection but left comments in # find the center point of all the matched features # account for the width of the needle image that appears on the left # drawn the found center point on the output image # display the processed image cv.imshow('Keypoint Search', match_image) cv.waitKey(1) res = True break return res ships_to_avoid = loadImages(glob.glob(r"C:\Users\*.png")) needle_kp1_desc = preProcessNeedle(ships_to_avoid) if shipDetection(needle_kp1_desc): # do something with the output
问题解答
这不是关键点检测算法的固有局限,是当前实现的预处理逻辑、参数选择没有适配文本颜色随机变化的场景,调整后完全可以实现紫色文本的稳定检测。
失效原因很直接:当前实现用ORB直接在BGR彩色图像上计算特征点和描述子,ORB的核心是基于局部区域的灰度梯度识别角点、生成匹配特征。白色文本和深色星空背景的灰度差极大,局部梯度特征非常显著,所以匹配效果好;紫色文本和暗星空背景的灰度差远小于白色文本,加上所用模板是白色文本截取的,彩色维度的局部特征和紫色文本完全不匹配,自然凑不齐足够的有效匹配点。
具体调整步骤如下:
- 统一做灰度转换:所有输入图像(模板图、实时截屏的检测区域)在送入ORB检测前,全部用
cv.cvtColor(img, cv.COLOR_BGR2GRAY)转成单通道灰度图,彻底消除文本颜色变化带来的彩色特征偏差。 - 增强局部对比度:转灰度后加一步对比度拉伸,用
cv.convertScaleAbs(gray_img, alpha=1.8, beta=0)拉大文本和星空背景的灰度差,alpha值可以根据实际效果在1.5~2.5区间调整,核心是把暗背景压暗、不同颜色的亮文本提亮,强化边缘梯度特征。 - 适配文本特征调参:当前设置的patchSize=32过大,游戏文本笔画细,局部特征范围小,把ORB的patchSize调到1216,edgeThreshold保持0即可;检测区域仅70*150像素,nfeatures设为500足够,不需要开到3000。FLANN匹配的LSH参数把key_size调到20、table_number调到8,搜索checks调到100,Lowe比率匹配阈值从0.7放宽到0.75,最小有效匹配数从40降到1015即可,小范围检测不需要过多匹配点。
- 鲁棒性可选优化:如果要彻底规避颜色、背景亮度波动的影响,可以在转灰度后加一步自适应阈值二值化:
cv.adaptiveThreshold(gray_img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2),直接把任意颜色的文本统一成和背景高对比的二值形态,后续不管文本变什么颜色都能稳定检测。
另外当前代码有个显性bug:match_keypoints函数定义只接收3个入参,调用时传入了4个参数,且函数仅返回3个值,调用时却接收4个返回值,运行时会直接报错,需要先把参数和返回值对齐。
内容的提问来源于stack exchange,提问作者mak47
相关产品推荐
相关产品推荐

