调试计算机视觉应用程序最常见的方法是什么?通常答案是临时的、拼凑的、自定义代码,这些代码必须从代码中删除才能进行发布编译。
在本教程中,我们将展示如何改用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:890
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
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调用只是显示图像(类似于imshow),并使用imgIdString作为注释。图像将添加到可视化调试GUI的概述选项卡中,并且cvv调用将阻塞。
图像
然后可以选择并查看图像。
图像
无论何时想要继续执行代码,即取消阻塞cvv调用,都可以一直继续到下一个cvv调用(Step),一直继续到最后一个cvv调用(*>>*),或者运行应用程序直到它退出(Close)。
我们决定按下绿色的Step按钮。
- 接下来的cvv调用用于调试各种滤波操作,即以图片作为输入并返回图片作为输出的操作。与每个cvv调用一样,您首先会进入概述。
图像
我们决定不关心转换为灰度,并按下Step。
如果打开滤波器调用,您将进入所谓的“DefaultFilterView”。两张图像并排显示,您可以(同步)放大它们。
图像
当您放大到非常高的级别时,每个像素都用其数值进行注释。
图像
我们按下Step两次,查看膨胀后的图像。
显示两张图像的DefaultFilterView
图像
现在我们使用右上角的View选择器并选择“DualFilterView”。我们选择“Changed Pixels”作为过滤器并应用它(中间图像)。
图像
在仔细查看这些图像之后,也许使用了不同的视图、过滤器或其他GUI功能,我们决定让程序运行完毕。因此,我们按下黄色的*>>*按钮。
程序将在
处阻塞,并显示包含期间传递给cvv的所有内容的概述。
图像
cvv debugDMatch调用用于存在两张图像的情况,每张图像都有一组相互匹配的描述符。
我们将两张图像、两组关键点及其匹配传递给可视化调试模块。
由于我们想要查看匹配项,因此我们在概述中使用过滤器功能(*#type match*)仅显示匹配调用。
图像
我们想更仔细地查看其中一个,例如调整使用匹配的参数。该视图具有多种显示关键点和匹配项的方式的设置。此外,还有一个鼠标悬停工具提示。
图像
我们看到(可视化调试!)有很多错误匹配。我们决定只应显示70%的匹配项——那些匹配距离最低的70%匹配项。
图像
成功减少了视觉干扰后,我们想要更清楚地看到两幅图像之间发生了什么变化。我们选择“TranslationMatchView”,它以不同的方式显示关键点匹配的位置。
图像
很容易看出,在两张图像之间,杯子向左移动了。
尽管cvv完全是关于交互式地查看计算机视觉错误,但这由一个“RawView”补充,该视图允许查看底层的数值数据。
图像
- cvv GUI中包含许多更有用的功能。例如,可以对概述选项卡进行分组。
图像
结果
- 通过在我们的计算机视觉程序中添加具有表达力的行,我们可以通过不同的可视化方式交互式地调试它。
- 开发/调试完成后,我们不必删除这些行。我们只需禁用cvv调试(cmake -DCVV_DEBUG_MODE=OFF或不带-DCVVISUAL_DEBUGMODE的g++),我们的程序就可以在没有任何调试开销的情况下运行。
享受计算机视觉的乐趣!