OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
计算机视觉应用的交互式可视化调试

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

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

目标

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

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

代码

示例代码

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

如果程序在没有可视化调试的情况下编译(参见下面的 CMakeLists.txt),唯一的结果是命令行中打印的一些信息。我们希望演示仅仅几行 cvv 命令能增加多少调试或开发功能。

1// system includes
2#include <iostream>
3
4// library includes
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 // setup video capture
53 cv::VideoCapture capture(0);
54 if (!capture.isOpened()) {
55 std::cout << "Could not open VideoCapture" << std::endl;
56 return 1;
57 }
58
59 if (res_w>0 && res_h>0) {
60 printf("Setting resolution to %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 // capture a frame
77 cv::Mat imgRead;
78 capture >> imgRead;
79 printf("%d: image captured\n", imgId);
80
81 std::string imgIdString{"imgRead"};
82 imgIdString += toString(imgId);
83 cvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str());
84
85 // convert to grayscale
86 cv::Mat imgGray;
87 cv::cvtColor(imgRead, imgGray, COLOR_BGR2GRAY);
88 cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray");
89
90 // detect ORB features
91 std::vector<cv::KeyPoint> keypoints;
92 cv::Mat descriptors;
93 detector->detectAndCompute(imgGray, cv::noArray(), keypoints, descriptors);
94 printf("%d: detected %zd keypoints\n", imgId, keypoints.size());
95
96 // match them to previous image (if available)
97 if (!prevImgGray.empty()) {
98 std::vector<cv::DMatch> matches;
99 matcher.match(prevDescriptors, descriptors, matches);
100 printf("%d: all matches size=%zd\n", imgId, matches.size());
101 std::string allMatchIdString{"all matches "};
102 allMatchIdString += toString(imgId-1) + "<->" + toString(imgId);
103 cvv::debugDMatch(prevImgGray, prevKeypoints, imgGray, keypoints, matches, CVVISUAL_LOCATION, allMatchIdString.c_str());
104
105 // remove worst (as defined by match distance) bestRatio quantile
106 double bestRatio = 0.8;
107 std::sort(matches.begin(), matches.end());
108 matches.resize(int(bestRatio * matches.size()));
109 printf("%d: best matches size=%zd\n", imgId, matches.size());
110 std::string bestMatchIdString{"best " + toString(bestRatio) + " matches "};
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
如果数组没有元素,则返回 true。
int64_t int64
n 维密集数组类
定义 mat.hpp:830
cv::getTickFrequency
double getTickFrequency()
用于指定图像或矩形大小的模板类。
Definition types.hpp:335
用于从视频文件、图像序列或摄像头捕获视频的类。
Definition videoio.hpp:772
@ NORM_HAMMING
定义 base.hpp:199
std::shared_ptr< _Tp > Ptr
Definition cvstd_wrapper.hpp:23
InputOutputArray noArray()
template<typename _Tp , int m, int n>
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)
将已填充的DMatch <dmatch> 添加到调试GUI。
定义 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)
使用调试框架比较两张图像(其中第二张图像应为...的结果)
定义 filter.hpp:36
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
将图像从一个颜色空间转换为另一个颜色空间。
int main(int argc, char *argv[])
定义 highgui_qt.cpp:3
定义 core.hpp:107
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")
# (un)set: 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 调用只是简单地显示图像(类似于 imshow),并以 imgIdString 作为注释。
    cvv::showImage(imgRead, CVVISUAL_LOCATION, imgIdString.c_str());
    图像被添加到可视化调试 GUI 的概览标签页中,并且 cvv 调用会阻塞。
image

然后可以选择并查看图像

image

无论何时您想在代码中继续执行,即解除 cvv 调用的阻塞,您可以选择继续直到下一个 cvv 调用 (Step),继续直到最后一个 cvv 调用 (*>>*) 或运行应用程序直到其退出 (Close)。

我们决定按下绿色的 Step 按钮。

  1. 接下来的 cvv 调用用于调试各种滤镜操作,即以一张图片作为输入并返回一张图片作为输出的操作。
    cvv::debugFilter(imgRead, imgGray, CVVISUAL_LOCATION, "to gray");
    与每次 cvv 调用一样,您首先会进入概览。
image

我们决定不关心灰度转换,然后按下 Step

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

如果您打开滤镜调用,您将进入所谓的“DefaultFilterView”。两张图像会并排显示,您可以(同步地)放大它们。

image

当您放大到非常高的级别时,每个像素都会标注其数值。

image

我们按两次 Step 键,然后查看膨胀后的图像。

cvv::debugFilter(imgEdges, imgEdgesDilated, CVVISUAL_LOCATION, "dilated edges");

DefaultFilterView 显示两张图像

image

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

image

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

程序将在以下位置阻塞:

并显示在此期间传递给 cvv 的所有内容的概览。

image
  1. cvv debugDMatch 调用用于在有两张图像且每张图像都带有一组相互匹配的描述符的情况下。

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

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

    由于我们想查看匹配,我们使用概览中的过滤功能(*#type match*)来只显示匹配调用。

image

我们想仔细查看其中一个,例如调整使用匹配的参数。该视图有多种设置来显示关键点和匹配。此外,还有一个鼠标悬停提示。

image

我们看到(可视化调试!)有许多不好的匹配。我们决定只显示 70% 的匹配——那些匹配距离最低的 70%。

image

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

image

很容易看出,在这两张图像之间,杯子向左移动了。

尽管 cvv 主要是关于交互式地查看计算机视觉错误,但它还补充了“RawView”,允许查看底层的数值数据。

image
  1. cvv GUI 中包含许多其他有用的功能。例如,可以对概览标签页进行分组。
image

结果

  • 通过向我们的计算机视觉程序添加一些富有表现力的代码行,我们可以通过不同的可视化方式进行交互式调试。
  • 一旦我们完成开发/调试,就不必删除这些代码行。我们只需禁用 cvv 调试(cmake -DCVV_DEBUG_MODE=OFF 或 g++ 不带 -DCVVISUAL_DEBUGMODE),我们的程序就会运行,没有任何调试开销。

享受计算机视觉!