调试计算机视觉应用最常用的方法是什么?通常,答案是暂时的、拼凑的自定义代码,从发布编译的代码中必须将其移除。
在本教程中,我们将展示如何使用 cvv 模块(opencv2/cvv.hpp)的可视化调试功能。
目标
在本教程中,您将学习如何
- 将 cvv 调试调用添加到您的应用程序
- 使用可视化调试 GUI
- 启用和禁用编译期间的可视化调试功能(禁用时,运行时开销为零)
代码
示例代码
- 捕获图像(videoio),例如,来自网络摄像头,
- 对每张图像应用一些滤镜(imgproc),
- 检测图像特征并将其与前一张图像匹配(features2d)。
如果在不启用可视化调试的情况下编译程序(请参阅以下 CMakeLists.txt),唯一的结果是一些信息打印到命令行。我们希望通过仅使用几行 cvv 命令来展示已添加了多少调试或开发功能。
7#include <opencv2/imgproc/types_c.h>
9#include <opencv2/videoio/videoio_c.h>
11#define CVVISUAL_DEBUGMODE
21template<
class T> std::string toString(
const T& p_arg)
34main(
int argc,
char** argv)
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 }";
45 if (parser.has(
"help")) {
46 parser.printMessage();
49 int res_w = parser.get<
int>(
"width");
50 int res_h = parser.get<
int>(
"height");
54 if (!capture.isOpened()) {
55 std::cout <<
"无法打开 VideoCapture" << std::endl;
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);
67 std::vector<cv::KeyPoint> prevKeypoints;
70 int maxFeatureCount = 500;
71 Ptr<ORB> detector = ORB::create(maxFeatureCount);
75 for (
int imgId = 0; imgId < 10; imgId++) {
79 printf(
"%d: 已捕获图像\n", imgId);
81 std::string imgIdString{
"imgRead"};
82 imgIdString += toString(imgId);
91 std::vector<cv::KeyPoint> keypoints;
93 detector->detectAndCompute(imgGray,
cv::noArray(), keypoints, descriptors);
94 printf(
"%d: 检测到 %zd 个关键点\n", imgId, keypoints.size());
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);
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);
115 prevImgGray = imgGray;
116 prevKeypoints = keypoints;
117 prevDescriptors = descriptors;
蛮力描述符匹配器。
定义 features2d.hpp:1247
设计用于命令行解析。
定义 utility.hpp:820
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`
`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`
)
` 说明`
- 我们使用上述 `CmakeLists.txt` 编译程序,其中包含选项 `CVV_DEBUG_MODE=ON`(`cmake -DCVV_DEBUG_MODE=ON`),或通过添加相应的定义 `CVVISUAL_DEBUGMODE` 到我们的编译器(例如,`g++ -DCVVISUAL_DEBUGMODE`)。
- 第一个 `cvv` 调用只使用 `imgIdString` 作为注释显示了图像(类似于 `imshow`)。图片被添加到视觉调试 GUI 中的概览选项卡,并且 `cvv` 调用会被阻止。
图像
此时可以选择图像并查看。
图像
每当你想在代码中继续,即可解除 `cvv` 调用的锁定,你可以继续执行到下一个 `cvv` 调用(“步入”),继续执行到最后一个 `cvv` 调用(“>>”),或运行应用程序直至其退出(“关闭”)。
我们决定按绿色“步入”按钮。
- 下一个 `cvv` 调用用于调试各种滤波器操作,即以一张图片作为输入并将图片作为输出返回的操作。和任何 `cvv` 调用一样,你最终都会进入概览。
图像
我们决定不关心转换为灰度,然后按“步入”。
如果你打开滤波器调用,你最终会进入所谓的“`DefaultFilterView`”。两张图像并排显示,而且你可以(同步)放大它们。
图像
在非常高的缩放级别时,每个像素会用数值标注。
图像
我们点击Step 按钮两次,查看膨胀后的图像。
既显示两幅图像的 DefaultFilterView
图像
现在,我们使用右上角的View 选择器,选择“DualFilterView”。我们选择“Changed Pixels”作为过滤器,并应用它(中间图像)。
图像
当我们使用不同的视图、过滤器或其他 GUI 功能仔细查看这些图像后,我们决定让程序继续运行。因此,我们点击黄色 *>>* 按钮。
程序将在以下位置阻塞
,并显示概述,其中包含在 meantime 传递给 cvv 的所有内容。
图像
cvv debugDMatch 调用用于同时匹配两组描述符的两幅图像的情况。
我们将两幅图像、两组关键点及其匹配传递给可视调试模块。
由于我们希望了解匹配情况,因此我们使用概述中的过滤器功能(*#type match*)仅显示匹配调用。
图像
我们希望仔细查看其中一个,例如调整匹配参数。这个视图提供了多种关键点和匹配显示设置。此外,它还提供鼠标悬浮提示工具。
图像
我们发现(可视调试!)有很多错误的匹配。我们决定只显示 70% 的匹配 - 70% 的匹配距离最短。
图像
成功减少视觉干扰后,我们希望更清楚地了解这两幅图像之间发生了什么变化。我们选择“TranslationMatchView”,使用不同的方式显示关键点匹配到的位置。
图像
可以轻松看到,两幅图像之间的过程中杯子被移动到左边。
虽然 cvv 的主要作用是交互式查看计算机视觉错误,但它还配有“RawView”,可以查看底层数字数据。
图像
- cvv GUI 中包含许多其他有用的功能。例如,可以对概述选项卡进行分组。
图像
结果
- 通过向我们的计算机视觉程序添加视图表达行,我们可以通过不同的可视化效果进行交互式调试。
- 完成开发/调试后,无需删除这些行。我们只需禁用 cvv 调试(cmake -DCVV_DEBUG_MODE=OFF 或没有 -DCVVISUAL_DEBUGMODE 的 g++),我们的程序就能在不产生任何调试开销的情况下运行。
享受计算机视觉!