计算机系统应用教程网站

网站首页 > 技术文章 正文

OpenCV目标跟踪GOTURN:基于深度学习的目标跟踪

btikc 2024-08-30 13:02:54 技术文章 14 ℃ 0 评论

在这篇文章中,我们将了解一种名为 GOTURN 的基于深度学习的对象跟踪算法。 GOTURN 的原始实现是在 Caffe 中,但它已被移植到 OpenCV 跟踪 API,我们将使用这个 API 在 C++ 和 Python 中演示 GOTURN。

1. 什么是目标跟踪?

对象跟踪的目标是跟踪视频序列中的对象。使用视频序列的帧和边界框来初始化跟踪算法,以指示我们感兴趣跟踪的对象的位置。跟踪算法为所有后续帧输出一个边界框。

2. 什么是GOTURN?

GOTURN 是 Generic Object Tracking Using Regression Networks 的缩写,是一种基于深度学习的跟踪算法。

之前帧与当前帧送入已训练好的神经网络中,最终获得当前帧的跟踪输出。

大多数跟踪算法都是在线训练的。换句话说,跟踪算法在运行时学习它所跟踪的对象的外观。

因此,许多实时跟踪器依赖于通常比基于深度学习的解决方案快得多的在线学习算法。

GOTURN 通过离线学习对象的运动改变了我们将深度学习应用于跟踪问题的方式。 GOTURN 模型在数千个视频序列上进行训练,不需要在运行时执行任何学习。

3. GOTURN是如何工作的?

GOTURN 将两个裁剪的帧作为输入,并在第二帧中输出对象周围的边界框。

GOTURN是使用从数千个视频中剪切的一对帧来训练的。

在第一帧(也称为前一帧)中,对象的位置是已知的,并且该帧被裁剪成对象周围边界框的两倍大小。第一个裁剪帧中的对象始终居中。

需要预测对象在第二帧(也称为当前帧)中的位置。用于裁剪第一帧的边界框也用于裁剪第二帧。因为物体可能已经移动了,所以物体不在第二帧的中心。

训练卷积神经网络 (CNN) 以预测第二帧中边界框的位置。

4.GOTURN架构

在上一节中,我们只是将 CNN 显示为一个黑盒子。现在,让我们看看盒子里有什么。

如前所述,它需要两个裁剪的帧作为输入。请注意,显示在下面的前一帧居中,我们的目标是找到顶部显示的当前帧的边界框。

两帧都通过一组卷积层。这些层只

是 CaffeNet 架构的前五个卷积层。这些卷积层的输出(即 pool5 特征)被连接成一个长度为 4096 的向量。这个向量输入到 3 个全连接层。最后一个全连接层最终连接到输出层,输出层包含代表边界框顶部和底部点的 4 个节点。

5.如何在 OpenCV 中使用 GOTURN

作者为 GOTURN 发布了一个 caffe 模型。您可以使用 Caffe 进行尝试,但在本教程中,我们将使用 OpenCV 的跟踪 API。以下是您需要遵循的步骤。

1.下载GOTURN模型文件

你可以在这个链接中下载GOTURN caffemodel和prototxt文件。模型文件被分割成4个文件,在解压缩之前需要合并这些文件(参见步骤2)。

或者,您可以使用这个dropbox链接来下载模型。请注意下载这个文件可能需要很长时间,因为它大约有370 MB!如果您使用这种方法,请跳过步骤2:合并压缩文件。通过GitHub共享的GOTURN模型文件被分成4个不同的文件,因为模型文件比较大。在解压缩之前,需要合并这些文件。OSX和Linux用户可以使用以下命令来做到这一点。

# MAC and Linux Users
cat goturn.caffemodel.zip* > goturn.caffemodel.zip


在 Windows 上,可以使用 7-zip 合并文件。将模型文件移动到当前目录:OpenCV 中的 GOTURN 实现期望模型文件存在于执行可执行文件的目录中。因此,将其移动到当前目录。

下面共享的代码已经在OpenCV 3.4.1上进行了测试(使用opencv_contrib编译)。以前的版本可能无法工作。

假设您已经下载了代码,让我们看看如何使用跟踪器。

  • 1.创建跟踪器:首先,我们需要创建一个 GOTURN 跟踪器类的实例。

C++

// Create tracker
Ptr<Tracker> tracker = TrackerGOTURN::create();


Python

# Create tracker
tracker = cv2.TrackerGOTURN_create()


  • 2.读取视频帧:接下来,我们读取一个视频帧

C++

// Read video
VideoCapture video("chaplin.mp4");

// Exit if video is not opened
if(!video.isOpened())
{
cout << "Could not read video file" << endl;
return EXIT_FAILURE;
}

// Read first frame
Mat frame;
if (!video.read(frame))
{
cout << "Cannot read video file" << endl;
return EXIT_FAILURE;
}


Python

# Read video
video = cv2.VideoCapture("chaplin.mp4")
# Exit if video not opened
if not video.isOpened():
print("Could not open video")
sys.exit()
# Read first frame
ok,frame = video.read()
if not ok:
print("Cannot read video file")
sys.exit()


  • 3.定义边界框:我们需要选择我们想要在视频中跟踪的对象。这是通过定义边界框或选择ROI来完成的。在我们的示例中,我们硬编码了边界框,但您可以使用cv2.selectROI调用GUI来查找感兴趣的区域。

C++

// 定义初始的边界框
Rect2d bbox(287, 23, 86, 320);
// 取消注释下面的行以选择一个不同的边界框
//bbox = selectROI(frame, false);


Python

# 定义初始的边界框
bbox = (276, 23, 86, 320)
# 取消注释下面的行以选择一个不同的边界框
#bbox = cv2.selectROI(frame, False)


  • 4.初始化跟踪器:跟踪器将第一帧和我们要跟踪的对象周围的边界框作为输入。

C++

// 用第一帧和包围框初始化跟踪器
tracker->init(frame, bbox);

Python

# 用第一帧和包围框初始化跟踪器
ok = tracker.init(frame,bbox)
  • 5.预测新帧中的边界框:最后,我们遍历视频中的所有帧,并使用 tracker.update 找到新帧的边界框。其余代码仅用于计时和显示。

C++

while(video.read(frame))
{
// 启动计时器
double timer = (double)getTickCount();

// 更新跟踪结果
bool ok = tracker->update(frame, bbox);

// 计算帧率(FPS)
float fps = getTickFrequency() / ((double)getTickCount() - timer);

if (ok)
{
// 跟踪成功
rectangle(frame, bbox, Scalar( 255, 0, 0 ), 2, 1 );
}
else
{
// 跟踪失败
putText(frame, "Tracking failure detected", Point(100,80), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0,0,255),2);
}

// 帧上显示跟踪类型
putText(frame, "GOTURN Tracker", Point(100,20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50),2);

// 帧上显示帧率FPS
putText(frame, "FPS : " + SSTR(int(fps)), Point(100,50), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50), 2);

// 显示帧
imshow("Tracking", frame);

// 按ESC退出
if(waitKey(1) == 27) break;
}


注意: 当跟踪器失败时,tracker.update 返回 0 (false)。如果我们将跟踪器与检测器一起使用,则可以使用此信息。当跟踪器发生故障时,检测器可用于检测对象并重新初始化跟踪器。

Python

while True:
# Read a new frame
ok, frame = video.read()
if not ok:
break
# Start timer
timer = cv2.getTickCount()
# Update tracker
ok, bbox = tracker.update(frame)
# Calculate Frames per second (FPS)
fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);
# Draw bounding box
if ok:
# Tracking success
p1 = (int(bbox[0]), int(bbox[1]))
p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1)
else :
# Tracking failure
cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2)
# Display tracker type on frame
cv2.putText(frame, "GOTURN Tracker", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2);
# Display FPS on frame
cv2.putText(frame, "FPS : " + str(int(fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2);
# Display result
cv2.imshow("Tracking", frame)

# Exit if ESC pressed
k = cv2.waitKey(1) & 0xff
if k == 27:
break


6.完整代码

C++

#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>
using namespace cv;
using namespace std;
#define SSTR( x ) static_cast< std::ostringstream & >( \
( std::ostringstream() << std::dec << x ) ).str()

int main(int argc, char **argv)
{
// Create tracker
Ptr<Tracker> tracker = TrackerGOTURN::create();
// Read video
VideoCapture video("chaplin.mp4");

// Exit if video is not opened
if(!video.isOpened())
{
cout << "Could not read video file" << endl;
return EXIT_FAILURE;
}

// Read first frame
Mat frame;
if (!video.read(frame))
{
cout << "Cannot read video file" << endl;
return EXIT_FAILURE;
}

// Define initial boundibg box
Rect2d bbox(287, 23, 86, 320);

// Uncomment the line below to select a different bounding box
//bbox = selectROI(frame, false);

// Initialize tracker with first frame and bounding box
tracker->init(frame, bbox);

while(video.read(frame))
{
// Start timer
double timer = (double)getTickCount();

// Update the tracking result
bool ok = tracker->update(frame, bbox);

// Calculate Frames per second (FPS)
float fps = getTickFrequency() / ((double)getTickCount() - timer);

if (ok)
{
// Tracking success : Draw the tracked object
rectangle(frame, bbox, Scalar( 255, 0, 0 ), 2, 1 );
}
else
{
// Tracking failure detected.
putText(frame, "Tracking failure detected", Point(100,80), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0,0,255),2);
}

// Display tracker type on frame
putText(frame, "GOTURN Tracker", Point(100,20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50),2);

// Display FPS on frame
putText(frame, "FPS : " + SSTR(int(fps)), Point(100,50), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50), 2);

// Display frame.
imshow("Tracking", frame);

// Exit if ESC pressed.
if(waitKey(1) == 27) break;
}
return EXIT_SUCCESS;
}


Python

# Import modules
import cv2, sys, os
if not (os.path.isfile('goturn.caffemodel') and os.path.isfile('goturn.prototxt')):
errorMsg = '''
Could not find GOTURN model in current directory.
Please ensure goturn.caffemodel and goturn.prototxt are in the current directory
'''
print(errorMsg)
sys.exit()
# Create tracker
tracker = cv2.TrackerGOTURN_create()
# Read video
video = cv2.VideoCapture("chaplin.mp4")
# Exit if video not opened
if not video.isOpened():
print("Could not open video")
sys.exit()
# Read first frame
ok,frame = video.read()
if not ok:
print("Cannot read video file")
sys.exit()
# Define a bounding box
bbox = (276, 23, 86, 320)
# Uncomment the line below to select a different bounding box
#bbox = cv2.selectROI(frame, False)
# Initialize tracker with first frame and bounding box
ok = tracker.init(frame,bbox)
while True:
# Read a new frame
ok, frame = video.read()
if not ok:
break
# Start timer
timer = cv2.getTickCount()
# Update tracker
ok, bbox = tracker.update(frame)
# Calculate Frames per second (FPS)
fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);
# Draw bounding box
if ok:
# Tracking success
p1 = (int(bbox[0]), int(bbox[1]))
p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1)
else :
# Tracking failure
cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2)
# Display tracker type on frame
cv2.putText(frame, "GOTURN Tracker", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2);
# Display FPS on frame
cv2.putText(frame, "FPS : " + str(int(fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2);
# Display result
cv2.imshow("Tracking", frame)

# Exit if ESC pressed
k = cv2.waitKey(1) & 0xff
if k == 27:
break


7.GOTURN的优势和局限性

与其他基于深度学习的跟踪器相比,GOTURN 速度很快。它在 Caffe 的 GPU 上以 100FPS 的速度运行,在 OpenCV CPU 中以大约 20FPS 的速度运行。尽管跟踪器是通用的,但理论上可以通过使用特定类型的对象来偏置训练集,从而在特定对象(例如行人)上获得更好的结果。

我已经确定了 GOTURN 的一些弱点。请记住,这些观察结果是基于有限的测试,人们应该对它们持保留态度。另外,请注意,GOTURN 的 OpenCV 版本使用与 Caffe 版本不同的模型。以下意见适用于在没有作者任何指导的情况下创建的 OpenCV 版本。

  • 1.在存在训练集中的对象的情况下跟踪不在训练集中的对象:我正在跟踪我的手掌,当我将它移到我的脸上时,跟踪器锁定在脸上并且再也没有恢复。我试着用手掌遮住脸,看看能不能把追踪器从脸上取下来,但没有。然后,我尝试跟踪我的脸部并用手遮挡它,但跟踪器能够通过遮挡跟踪脸部。
  • 我的猜测是,训练集中的人脸比手掌多得多,因此当一张脸在附近时,它跟踪手会出现问题。当场景中有多个对象交互时,这可能是一个更普遍的问题。当场景中的对象靠近训练集中的跟踪对象时,跟踪器可能会锁定在训练集中的对象。
  • 2.跟踪对象的一部分:与整个对象相比,跟踪器似乎也很难跟踪对象的一部分。例如,当我试图用它来追踪我的指尖时,它最终会追踪到手。这可能是因为它不是针对对象的一部分进行训练,而是针对整个对象进行训练。
  • 3.缺乏运动信息:由于运动信息没有包含在两帧模型中,如果我们正在跟踪一个在一个方向上移动的物体(比如一张脸),它会被向另一个方向移动的类似物体(比如另一张脸)部分遮挡,跟踪器就有可能锁定错误的脸。这个问题可以通过使用第一帧作为前一帧来解决。

参考目录

https://learnopencv.com/goturn-deep-learning-based-object-tracking/

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表