用于检测视频中矩形形状(文本光标)的对象检测模型?

Object detection model for detecting rectangular shape (text cursor) in a video?

我目前正在进行一些研究,以检测和定位屏幕录制视频中的文本光标(您知道,闪烁的矩形形状指示您在计算机上键入时字符的位置)。为此,我使用自定义对象数据集训练了 YOLOv4 模型(我从 here 中获取了参考)并计划还实施 DeepSORT 来跟踪移动光标。

下面是我用来训练YOLOv4的训练数据示例:

这是我想要实现的目标:

您是否认为使用 YOLOv4 + DeepSORT 对这项任务来说被认为是矫枉过正?我问是因为到目前为止,模型只能成功检测到包含文本光标的视频帧的 70%-80%。如果它毕竟是矫枉过正,你知道可以为这个任务实施的任何其他方法吗?

无论如何,我计划不仅从 Visual Studio 代码 window,而且从浏览器(例如,Google Chrome)检测文本光标和文本处理器(例如 Microsoft Word)。像这样:

我正在考虑将滑动 Window 方法作为替代方法,但根据我的阅读,该方法可能会消耗大量资源并且执行速度较慢。我也在考虑从 OpenCV (like this) 进行模板匹配,但我认为它不会比 YOLOv4 表现得更好更快。

约束是关于性能速度(即,给定时间量可以处理多少帧)和检测精度(即,我想避免将字母 'l' 或“1”检测为文本光标,因为这些字符在某些字体中是相似的)。但我认为更高的精度和更慢的 FPS 是可以接受的。

我目前正在为此使用 Python、Tensorflow 和 OpenCV。 非常感谢!

如果光标是屏幕上唯一的移动对象,这将起作用。这是之前和之后:

之前:

之后:

代码:

import cv2
import numpy as np

BOX_WIDTH = 10
BOX_HEIGHT = 20

def process_img(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    kernel = np.ones((5, 5))
    img_canny = cv2.Canny(img_gray, 50, 50)
    return img_canny

def get_contour(img):
    contours, hierarchies = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    if contours:
        return max(contours, key=cv2.contourArea)

def get_line_tip(cnt1, cnt2):
    x1, y1, w1, h1 = cv2.boundingRect(cnt1)

    if h1 > BOX_HEIGHT / 2:
        if np.any(cnt2):
            x2, y2, w2, h2 = cv2.boundingRect(cnt2)
            if x1 < x2:
                return x1, y1
        return x1 + w1, y1

def get_rect(x, y):
    half_width = BOX_WIDTH // 2
    lift_height = BOX_HEIGHT // 6
    return (x - half_width, y - lift_height), (x + half_width, y + BOX_HEIGHT - lift_height)

cap = cv2.VideoCapture("screen_record.mkv")
success, img_past = cap.read()

cnt_past = np.array([])
line_tip_past = 0, 0

while True:
    success, img_live = cap.read()

    if not success:
        break

    img_live_processed = process_img(img_live)
    img_past_processed = process_img(img_past)

    img_diff = cv2.bitwise_xor(img_live_processed, img_past_processed)
    cnt = get_contour(img_diff)

    line_tip = get_line_tip(cnt, cnt_past)

    if line_tip:
        cnt_past = cnt
        line_tip_past = line_tip
    else:
        line_tip = line_tip_past

    rect = get_rect(*line_tip)
    img_past = img_live.copy()
    cv2.rectangle(img_live, *rect, (0, 0, 255), 2)

    cv2.imshow("Cursor", img_live)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
    
cv2.destroyAllWindows()

分解:

  1. 导入必要的库:
import cv2
import numpy as np
  1. 根据光标的大小定义跟踪框的大小:
BOX_WIDTH = 10
BOX_HEIGHT = 20
  1. 定义一个函数来将帧处理成边缘:
def process_img(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    kernel = np.ones((5, 5))
    img_canny = cv2.Canny(img_gray, 50, 50)
    return img_canny
  1. 定义一个函数来检索图像中面积最大的轮廓(光标不需要很大也能正常工作,如果需要可以很小):
def get_contour(img):
    contours, hierarchies = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    if contours:
        return max(contours, key=cv2.contourArea)
  1. 定义一个包含 2 个轮廓的函数,一个是光标的轮廓 + 当前帧的一些文本,另一个是轮廓 + 光标轮廓的一些杂散文本 + 来自帧之前。通过这两个轮廓,我们可以识别光标是向左移动还是向右移动:
def get_line_tip(cnt1, cnt2):
    x1, y1, w1, h1 = cv2.boundingRect(cnt1)

    if h1 > BOX_HEIGHT / 2:
        if np.any(cnt2):
            x2, y2, w2, h2 = cv2.boundingRect(cnt2)
            if x1 < x2:
                return x1, y1
        return x1 + w1, y1
  1. 定义一个接受光标尖端点的函数,return 一个基于之前定义的 BOX_WIDTHBOX_HEIGHT 常量的框:
def get_rect(x, y):
    half_width = BOX_WIDTH // 2
    lift_height = BOX_HEIGHT // 6
    return (x - half_width, y - lift_height), (x + half_width, y + BOX_HEIGHT - lift_height)
  1. 为视频定义一个捕获设备,并从视频的开头删除一帧并将其存储在一个变量中,该变量将用作每帧之前的帧。还为过去的轮廓和过去的线尖定义临时值:
cap = cv2.VideoCapture("screen_record.mkv")
success, img_past = cap.read()

cnt_past = np.array([])
line_tip_past = 0, 0
  1. 使用 while 循环,并从视频中读取。处理视频中的帧和该帧之前的帧:
while True:
    success, img_live = cap.read()
    if not success:
        break
    img_live_processed = process_img(img_live)
    img_past_processed = process_img(img_past)
  1. 对于处理后的帧,我们可以使用 cv2.bitwise_xor 方法找到帧之间的差异,以获取屏幕上的移动位置。然后,我们可以使用定义的 get_contour 函数找到 2 帧之间的运动轮廓:
    img_diff = cv2.bitwise_xor(img_live_processed, img_past_processed)
    cnt = get_contour(img_diff)
  1. 有了轮廓,我们就可以利用定义的get_line_tip函数来找到光标的尖端。如果找到提示,将其保存到 line_tip_past 变量中以供下一次迭代使用,如果未找到提示,我们可以将之前保存的提示用作当前提示:
    line_tip = get_line_tip(cnt, cnt_past)

    if line_tip:
        cnt_past = cnt
        line_tip_past = line_tip
    else:
        line_tip = line_tip_past
  1. 现在我们使用光标提示和我们之前定义的get_rect函数定义一个矩形,并将其绘制到当前帧上。但是在绘制之前,我们将帧保存为下一次迭代当前帧之前的帧:
    rect = get_rect(*line_tip)
    img_past = img_live.copy()
    cv2.rectangle(img_live, *rect, (0, 0, 255), 2)
  1. 最后,我们显示框架:
    cv2.imshow("Cursor", img_live)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
    
cv2.destroyAllWindows()