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

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

在本教程中,我们将展示如何改用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 // 解析器键
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, "转换为灰度");
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:890
n维密集数组类
定义 mat.hpp:829
bool empty() const
如果数组没有元素,则返回true。
用于指定图像或矩形大小的模板类。
定义 types.hpp:335
用于从视频文件、图像序列或摄像头捕获视频的类。
定义 videoio.hpp:766
@ NORM_HAMMING
定义 base.hpp:199
std::shared_ptr< _Tp > Ptr
定义 cvstd_wrapper.hpp:23
InputOutputArray noArray()
返回一个空的InputArray或OutputArray。
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)
使用debug-framework比较两张图像(其中第二张图像是……的结果)。
定义 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")
# (取消)设置: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调用将阻塞。
图像

然后可以选择并查看图像。

图像

无论何时想要继续执行代码,即取消阻塞cvv调用,都可以一直继续到下一个cvv调用(Step),一直继续到最后一个cvv调用(*>>*),或者运行应用程序直到它退出(Close)。

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

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

我们决定不关心转换为灰度,并按下Step

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

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

图像

当您放大到非常高的级别时,每个像素都用其数值进行注释。

图像

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

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

显示两张图像的DefaultFilterView

图像

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

图像

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

程序将在

处阻塞,并显示包含期间传递给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++),我们的程序就可以在没有任何调试开销的情况下运行。

享受计算机视觉的乐趣!