OpenCV  4.10.0
Open Source Computer Vision
正在加载...
正在搜索...
无匹配项
计算机视觉应用的交互式可视化调试

调试计算机视觉应用最常用的方法是什么?通常,答案是暂时的、拼凑的自定义代码,从发布编译的代码中必须将其移除。

在本教程中,我们将展示如何使用 cvv 模块(opencv2/cvv.hpp)的可视化调试功能。

目标

在本教程中,您将学习如何

  • 将 cvv 调试调用添加到您的应用程序
  • 使用可视化调试 GUI
  • 启用和禁用编译期间的可视化调试功能(禁用时,运行时开销为零)

代码

示例代码

  • 捕获图像(videoio),例如,来自网络摄像头,
  • 对每张图像应用一些滤镜(imgproc),
  • 检测图像特征并将其与前一张图像匹配(features2d)。

如果在不启用可视化调试的情况下编译程序(请参阅以下 CMakeLists.txt),唯一的结果是一些信息打印到命令行。我们希望通过仅使用几行 cvv 命令来展示已添加了多少调试或开发功能。

1// 系统头文件
2#include <iostream>
3
4// 库头文件
5#include <opencv2/imgproc.hpp>
7#include <opencv2/imgproc/types_c.h>
8#include <opencv2/videoio.hpp>
9#include <opencv2/videoio/videoio_c.h>
10
11#define CVVISUAL_DEBUGMODE
17
18using namespace std;
19using namespace cv;
20
21template<class T> std::string toString(const T& p_arg)
22{
23 std::stringstream ss;
24
25 ss << p_arg;
26
27 return ss.str();
28}
29
30
31
32
33int
34main(int argc, char** argv)
35{
36 cv::Size* resolution = nullptr;
37
38 // parser keys
39 const char *keys =
40 "{ help h usage ? | | show this message }"
41 "{ width W | 0| camera resolution width. leave at 0 to use defaults }"
42 "{ height H | 0| camera resolution height. leave at 0 to use defaults }";
43
44 CommandLineParser parser(argc, argv, keys);
45 if (parser.has("help")) {
46 parser.printMessage();
47 return 0;
48 }
49 int res_w = parser.get<int>("width");
50 int res_h = parser.get<int>("height");
51
52 // 设置视频捕捉
53 cv::VideoCapture capture(0);
54 if (!capture.isOpened()) {
55 std::cout << "无法打开 VideoCapture" << std::endl;
56 return 1;
57 }
58
59 if (res_w>0 && res_h>0) {
60 printf("将分辨率设置为 %dx%d\n", res_w, res_h);
61 capture.set(CV_CAP_PROP_FRAME_WIDTH, res_w);
62 capture.set(CV_CAP_PROP_FRAME_HEIGHT, res_h);
63 }
64
65
66 cv::Mat prevImgGray;
67 std::vector<cv::KeyPoint> prevKeypoints;
68 cv::Mat prevDescriptors;
69
70 int maxFeatureCount = 500;
71 Ptr<ORB> detector = ORB::create(maxFeatureCount);
72
74
75 for (int imgId = 0; imgId < 10; imgId++) {
76 // 捕获帧
77 cv::Mat imgRead;
78 capture >> imgRead;
79 printf("%d: 已捕获图像\n", imgId);
80
81 std::string imgIdString{"imgRead"};
82 imgIdString += toString(imgId);
83 cvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str());
84
85 // 转换为灰度
86 cv::Mat imgGray;
87 cv::cvtColor(imgRead, imgGray, COLOR_BGR2GRAY);
88 cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray");
89
90 // 检测 ORB 特征
91 std::vector<cv::KeyPoint> keypoints;
92 cv::Mat descriptors;
93 detector->detectAndCompute(imgGray, cv::noArray(), keypoints, descriptors);
94 printf("%d: 检测到 %zd 个关键点\n", imgId, keypoints.size());
95
96 // 将它们与之前的图像进行匹配(如果可用)
97 if (!prevImgGray.empty()) {
98 std::vector<cv::DMatch> matches;
99 matcher.match(prevDescriptors, descriptors, matches);
100 printf("%d: 所有匹配项的大小=%zd\n", imgId, matches.size());
101 std::string allMatchIdString{"所有匹配项 "};
102 allMatchIdString += toString(imgId-1) + "<->" + toString(imgId);
103 cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, allMatchIdString.c_str());
104
105 // 移除最差的(由匹配距离定义)bestRatio 分位数
106 double bestRatio = 0.8;
107 std::sort(matches.begin(), matches.end());
108 matches.resize(int(bestRatio * matches.size()));
109 printf("%d: 最佳匹配项的大小=%zd\n", imgId, matches.size());
110 std::string bestMatchIdString{"最佳 " + toString(bestRatio) + " 匹配项 "};
111 bestMatchIdString += toString(imgId-1) + "<->" + toString(imgId);
112 cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, bestMatchIdString.c_str());
113 }
114
115 prevImgGray = imgGray;
116 prevKeypoints = keypoints;
117 prevDescriptors = descriptors;
118 }
119
121
122 return 0;
123}
#define CVVISUAL_LOCATION
使用宏所在位置作为值创建 CallMetaData 实例。
定义 call_meta_data.hpp:65
蛮力描述符匹配器。
定义 features2d.hpp:1247
设计用于命令行解析。
定义 utility.hpp:820
n 维度密集阵列类
定义 mat.hpp:812
bool empty() const
如果阵列没有元素,则返回 true。
用于指定图像或矩形大小的模板类。
定义 types.hpp:335
用于从视频文件、图像序列或摄像机捕获视频的类。
定义 videoio.hpp:731
@ NORM_HAMMING
定义 base.hpp:199
std::shared_ptr< _Tp > Ptr
定义 cvstd_wrapper.hpp:23
InputOutputArray noArray()
void finalShow()
最后一次将控件传递给调试窗口。
定义 final_show.hpp:23
static void debugDMatch(cv::InputArray img1, std::vector< cv::KeyPoint > keypoints1, cv::InputArray img2, std::vector< cv::KeyPoint > keypoints2, std::vector< cv::DMatch > matches, const impl::CallMetaData &data, const char *description=nullptr, const char *view=nullptr, bool useTrainDescriptor=true)
向调试 GUI 添加填入的 DMatch <dmatch>。
定义 dmatch.hpp:49
static void showImage(cv::InputArray img, impl::CallMetaData metaData=impl::CallMetaData(), const char *description=nullptr, const char *view=nullptr)
向调试 GUI 添加单张图像(类似于 imshow <>)。
定义 show_image.hpp:38
static void debugFilter(cv::InputArray original, cv::InputArray result, impl::CallMetaData metaData=impl::CallMetaData(), const char *description=nullptr, const char *view=nullptr)
使用 `debug-framework` 比较两张图像(其中第二张应是...的结果
定义 `filter.hpp:36`
`void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)`
将一张图像从一种颜色空间转换为另一种颜色空间。
`int main(int argc, char *argv[])`
定义 `highgui_qt.cpp:3`
文件存储的“黑盒”表示形式,该文件存储与磁盘上的一个文件相关联。
定义 `core.hpp:102`
STL 命名空间。
`cmake_minimum_required(VERSION 2.8)`
`project(cvvisual_test)`
`SET(CMAKE_PREFIX_PATH ~/software/opencv/install)`
`SET(CMAKE_CXX_COMPILER "g++-4.8")`
`SET(CMAKE_CXX_FLAGS "-std=c++11 -O2 -pthread -Wall -Werror")`
#(未)设置:`cmake -DCVV_DEBUG_MODE=OFF ..`
`OPTION(CVV_DEBUG_MODE "cvvisual-debug-mode" ON)`
`if(CVV_DEBUG_MODE MATCHES ON)`
`set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DCVVISUAL_DEBUGMODE")`
`endif()`
`FIND_PACKAGE(OpenCV REQUIRED)`
`include_directories(${OpenCV_INCLUDE_DIRS})`
`add_executable(cvvt main.cpp)`
`target_link_libraries(cvvt
`opencv_core opencv_videoio opencv_imgproc opencv_features2d
`opencv_cvv`
)

` 说明`

  1. 我们使用上述 `CmakeLists.txt` 编译程序,其中包含选项 `CVV_DEBUG_MODE=ON`(`cmake -DCVV_DEBUG_MODE=ON`),或通过添加相应的定义 `CVVISUAL_DEBUGMODE` 到我们的编译器(例如,`g++ -DCVVISUAL_DEBUGMODE`)。
  2. 第一个 `cvv` 调用只使用 `imgIdString` 作为注释显示了图像(类似于 `imshow`)。
    `cvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str());`
    图片被添加到视觉调试 GUI 中的概览选项卡,并且 `cvv` 调用会被阻止。
图像

此时可以选择图像并查看。

图像

每当你想在代码中继续,即可解除 `cvv` 调用的锁定,你可以继续执行到下一个 `cvv` 调用(“步入”),继续执行到最后一个 `cvv` 调用(“>>”),或运行应用程序直至其退出(“关闭”)。

我们决定按绿色“步入”按钮。

  1. 下一个 `cvv` 调用用于调试各种滤波器操作,即以一张图片作为输入并将图片作为输出返回的操作。
    `cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray");`
    和任何 `cvv` 调用一样,你最终都会进入概览。
图像

我们决定不关心转换为灰度,然后按“步入”。

`cvv::debugFilter(imgGray, imgGraySmooth, CVVISUAL_LOCATION, "smoothed");`

如果你打开滤波器调用,你最终会进入所谓的“`DefaultFilterView`”。两张图像并排显示,而且你可以(同步)放大它们。

图像

在非常高的缩放级别时,每个像素会用数值标注。

图像

我们点击Step 按钮两次,查看膨胀后的图像。

cvv::debugFilter(imgEdges, imgEdgesDilated, CVVISUAL_LOCATION, "膨胀边缘");

既显示两幅图像的 DefaultFilterView

图像

现在,我们使用右上角的View 选择器,选择“DualFilterView”。我们选择“Changed Pixels”作为过滤器,并应用它(中间图像)。

图像

当我们使用不同的视图、过滤器或其他 GUI 功能仔细查看这些图像后,我们决定让程序继续运行。因此,我们点击黄色 *>>* 按钮。

程序将在以下位置阻塞

,并显示概述,其中包含在 meantime 传递给 cvv 的所有内容。

图像
  1. cvv debugDMatch 调用用于同时匹配两组描述符的两幅图像的情况。

    我们将两幅图像、两组关键点及其匹配传递给可视调试模块。

    cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, allMatchIdString.c_str());

    由于我们希望了解匹配情况,因此我们使用概述中的过滤器功能(*#type match*)仅显示匹配调用。

图像

我们希望仔细查看其中一个,例如调整匹配参数。这个视图提供了多种关键点和匹配显示设置。此外,它还提供鼠标悬浮提示工具。

图像

我们发现(可视调试!)有很多错误的匹配。我们决定只显示 70% 的匹配 - 70% 的匹配距离最短。

图像

成功减少视觉干扰后,我们希望更清楚地了解这两幅图像之间发生了什么变化。我们选择“TranslationMatchView”,使用不同的方式显示关键点匹配到的位置。

图像

可以轻松看到,两幅图像之间的过程中杯子被移动到左边。

虽然 cvv 的主要作用是交互式查看计算机视觉错误,但它还配有“RawView”,可以查看底层数字数据。

图像
  1. cvv GUI 中包含许多其他有用的功能。例如,可以对概述选项卡进行分组。
图像

结果

  • 通过向我们的计算机视觉程序添加视图表达行,我们可以通过不同的可视化效果进行交互式调试。
  • 完成开发/调试后,无需删除这些行。我们只需禁用 cvv 调试(cmake -DCVV_DEBUG_MODE=OFF 或没有 -DCVVISUAL_DEBUGMODE 的 g++),我们的程序就能在不产生任何调试开销的情况下运行。

享受计算机视觉!