阅读(4587) (0)

如何使用OpenCV在“Microsoft Visual Studio”中构建应用程序

2017-08-23 11:08:51 更新

我在这里描述的一切将适用于C\C++ OpenCV 的界面。我从假设你已经阅读并成功完成了Windows中的安装教程。因此,在您进一步之前,请确保您具有包含OpenCV头文件和二进制文件的OpenCV目录,并按照此处所述设置环境变量设置OpenCV环境变量并将其添加到系统路径。

OpenCV_Install_Directory

由我们分发的Microsoft Windows操作系统上的OpenCV库位于动态链接库(DLL)中。这些优点是,库的所有内容只能在运行时按需加载,并且无数程序可能使用相同的库文件。这意味着如果您有十个使用OpenCV库的应用程序,则不需要为每个应用程序提供每个应用程序。当然,您需要在要运行应用程序的所有系统上安装OpenCV 的DLL。

另一种方法是使用具有lib扩展名的静态库。您可以通过使用我们的源文件来构建它们,如Windows中的“ 安装”教程中所述。当你使用这个库将内置在您的exe文件。因此,由于某些原因,用户将无法删除它们。作为一个缺点,您的应用程序将是更大的一个,因为它将需要更多的时间来加载它在启动期间。

要使用OpenCV构建应用程序,您需要做两件事情:

  • 告诉编译器OpenCV库的外观。您可以通过显示头文件来执行此操作。
  • 告诉链接器从何处获取OpenCV的功能或数据结构。

如果使用lib系统,则必须设置库文件所在的路径,并指定其中的哪一个。在构建期间,链接器将查找这些库,并将所有使用的函数和数据结构的定义和实现添加到可执行文件中。

如果您使用DLL系统,则必须再次指定所有这些,但是由于其他原因。这是一个Microsoft操作系统特定的东西。看来,链接器需要知道在DLL中哪里可以在运行时搜索数据结构或函数。此信息存储在lib文件中。不过,它们不是静态库。它们是所谓的导入库。这就是为什么当你在Windows中制作一些DLL时,你也会得到一些lib扩展库。很重要的是在运行时只需要DLL。

要将所有这些信息传递给Visual Studio IDE,您可以在全局范围内执行(所有未来的项目都将获得此信息)或本地(因此仅适用于当前项目)。全局的优势是你只需要做一次; 但是,所有这些信息都可能不合时宜地聚集所有的项目。在全局的情况下,如何执行此操作取决于您使用的Microsoft Visual Studio。有2008年和以前的版本和2010年的做法。在本教程的全局部分中,我将展示主要区别。

Visual Studio中项目的基础项目是一个解决方案。解决方案可能包含多个项目。项目是应用程序的构建块。每个项目都会实现一些东西,你将有一个主要的项目,你可以把这个项目的难题放在一起。在许多简单的应用程序(如许多教程将是)的情况下,您不需要将应用程序分解为模块。在这些情况下,您的主要项目将是唯一的现有项目。现在,通过File - > New - > Project菜单选项,在Visual Studio中创建一个新的解决方案。选择Win32控制台应用程序作为类型。输入其名称并选择要创建它的路径。然后在即将到来的对话框中确保创建一个空项目。

NewProjectVisualStudio

The local method

每个项目都是与其他项目分开构建的。由于这个每个项目都有自己的规则包。在这个规则中,包中存储了IDE需要知道的所有信息来构建项目。对于任何应用程序,至少有两种构建模式:发布和调试。在调试有许多功能,存在,因此,你可以找到并解决您的应用程序中更容易错误。相比之下,Release是一个优化的版本,其目标是使应用程序尽可能快地运行或尽可能小。您可能会认为,这些模式在构建期间也需要使用不同的规则。因此,每个构建模式都存在不同的规则包。这些规则包在IDE中作为项目属性调用,您可以使用Property Manager查看和修改它们。您可以使用View - > Property Pages(对于Visual Studio 2013起,进入View - > Other Windows - > Property Manager)。展开它,您可以看到现有的规则包(称为属性表)。

PropertyPageExample

这些真正有用的东西是您可以创建一个规则包一次,然后可以将其添加到新的项目中。创建一次并稍后重用。我们要创建一个新的属性表,其中包含编译器和链接器需要知道的所有规则。当然,我们将需要一个单独的调试和发布版本。启动调试一,如下图所示:

AddNewPropertySheet (1)

使用OpenCV_Debug名称。然后选择表格右键 - >属性。在下面我将展示在本地设置OpenCV规则,因为我发现不必用不使用自定义规则来污染项目。去C ++组通用条目和*“其他包含目录”*添加OpenCV包含的路径。如果没有*“C / C ++”*组,则应该将任何.c / .cpp文件添加到项目中。

$(OPENCV_DIR)\include

PropertySheetOpenCVInclude


当添加第三方库设置时,通常使用环境变量背后的权力是一个好主意。OpenCV库的完整位置可能会在每个系统上更改。此外,由于某些原因,您甚至可能会自动移动安装目录。如果您在属性表中给出明确的路径,那么当您将其进一步传递给具有不同OpenCV安装路径的其他人时,您的项目将最终不起作用。此外,修复这将需要手动修改每个显式路径。一个更优雅的解决方案是使用环境变量。任何您放入括号内的任何以美元符号开头的内容将在运行时替换为当前环境变量值。这里介绍了我们以前的教程中已经做出的环境变量设置OpenCV环境变量并将其添加到系统路径

下一步去链接器 - >常规下的*“附加库目录”*添加libs目录:

$(OPENCV_DIR)\ LIB

PropertySheetOpenCVLib

然后,您需要指定链接器应查看的库。要执行此操作,请转到链接器 - >输入,并在*“附加依赖关系”*条目下添加要使用的所有模块的名称:

PropertySheetOpenCVLibrariesDebug

the libraries的命名如下:

opencv_(The Name of the module)(The version Number of the library you use)d.lib

完整列表,最新版本将包含:

opencv_calib3d300d.lib
opencv_core300d.lib
opencv_features2d300d.lib
opencv_flann300d.lib
opencv_highgui300d.lib
opencv_imgcodecs300d.lib
opencv_imgproc300d.lib
opencv_ml300d.lib
opencv_objdetect300d.lib
opencv_photo300d.lib
opencv_shape300d.lib
opencv_stitching300d.lib
opencv_superres300d.lib
opencv_ts300d.lib
opencv_video300d.lib
opencv_videoio300d.lib
opencv_videostab300d.lib

最后的字母d表示这些是调试所需的库。现在点击确定保存,并在发布规则部分内使用新的属性。确保省略库名称中的d个字母,并保存属性表与其上方的保存图标。

PropertySheetOpenCVLibrariesRelease

您可以在项目目录中找到属性表。在这一点上,无论何时创建OpenCV项目,将它们备份到一些特殊的目录中,以便在将来随时随地掌握它,这是一个明智的决定。请注意,对于Visual Studio 2010,文件扩展名是道具,而在2008年,这是vsprops

PropertySheetInsideFolder

下一次当您创建一个新的OpenCV项目时,只需使用属性管理器中的“添加现有属性表...”菜单条目即可轻松添加OpenCV构建规则。

PropertyPageAddExisting

The global method

如果您发现将属性页面添加到每个项目中都太麻烦,您还可以将此规则添加到*“全局属性页”*。但是,这仅适用于附加的include和library目录。使用的库的名称仍然需要通过使用例如:属性页手动指定。

在Visual Studio 2008中,您可以在“工具” - >“选项” - >“项目和解决方案” - >“VC ++目录”下找到该文件。

VCDirectories2008

在Visual Studio 2010中,它已被移动到全局属性表,该属性表会自动添加到您创建的每个项目中:

VCDirectories2010

该过程与本地方法的情况相同。只需使用环境变量OPENCV_DIR添加包含目录。

Test it!

现在尝试下载我们的小测试源代码,或从OpenCV源的示例代码文件夹中获取。将其添加到您的项目并构建它。以下是其内容:

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
    if( argc != 2)
    {
     cout <<" Usage: display_image ImageToLoadAndDisplay" << endl;
     return -1;
    }
    Mat image;
    image = imread(argv[1], IMREAD_COLOR); // Read the file
    if( image.empty() ) // Check for invalid input
    {
        cout << "Could not open or find the image" << std::endl ;
        return -1;
    }
    namedWindow( "Display window", WINDOW_AUTOSIZE ); // Create a window for display.
    imshow( "Display window", image ); // Show our image inside it.
    waitKey(0); // Wait for a keystroke in the window
    return 0;
}

或者使用以下代码:

// Video Image PSNR and SSIM
#include <iostream> // for standard I/O
#include <string>   // for strings
#include <iomanip>  // for controlling float print precision
#include <sstream>  // string to number conversion

#include <opencv2/imgproc/imgproc.hpp>  // Gaussian Blur
#include <opencv2/core/core.hpp>        // Basic OpenCV structures (cv::Mat, Scalar)
#include <opencv2/highgui/highgui.hpp>  // OpenCV window I/O

using namespace std;
using namespace cv;

double getPSNR ( const Mat& I1, const Mat& I2);
Scalar getMSSIM( const Mat& I1, const Mat& I2);

void help()
{
    cout
        << "\n--------------------------------------------------------------------------" << endl
        << "This program shows how to read a video file with OpenCV. In addition, it tests the"
        << " similarity of two input videos first with PSNR, and for the frames below a PSNR "  << endl
        << "trigger value, also with MSSIM."<< endl
        << "Usage:"                                                                       << endl
        << "./video-source referenceVideo useCaseTestVideo PSNR_Trigger_Value Wait_Between_Frames " << endl
        << "--------------------------------------------------------------------------"   << endl
        << endl;
}
int main(int argc, char *argv[], char *window_name)
{
    help();
    if (argc != 5)
    {
        cout << "Not enough parameters" << endl;
        return -1;
    }
    stringstream conv;

    const string sourceReference = argv[1],sourceCompareWith = argv[2];
    int psnrTriggerValue, delay;
    conv << argv[3] << argv[4];       // put in the strings
    conv >> psnrTriggerValue >> delay;// take out the numbers

    char c;
    int frameNum = -1;          // Frame counter

    VideoCapture captRefrnc(sourceReference),
                 captUndTst(sourceCompareWith);

    if ( !captRefrnc.isOpened())
    {
        cout  << "Could not open reference " << sourceReference << endl;
        return -1;
    }

    if( !captUndTst.isOpened())
    {
        cout  << "Could not open case test " << sourceCompareWith << endl;
        return -1;
    }

    Size refS = Size((int) captRefrnc.get(CV_CAP_PROP_FRAME_WIDTH),
                     (int) captRefrnc.get(CV_CAP_PROP_FRAME_HEIGHT)),
         uTSi = Size((int) captUndTst.get(CV_CAP_PROP_FRAME_WIDTH),
                     (int) captUndTst.get(CV_CAP_PROP_FRAME_HEIGHT));

    if (refS != uTSi)
    {
        cout << "Inputs have different size!!! Closing." << endl;
        return -1;
    }

    const char* WIN_UT = "Under Test";
    const char* WIN_RF = "Reference";

    // Windows
            namedWindow(WIN_RF, CV_WINDOW_AUTOSIZE );
            namedWindow(WIN_UT, CV_WINDOW_AUTOSIZE );
            cvMoveWindow(WIN_RF, 400       ,            0);      //750,  2 (bernat =0)
            cvMoveWindow(WIN_UT, refS.width,            0);      //1500, 2

    cout << "Frame resolution: Width=" << refS.width << "  Height=" << refS.height
         << " of nr#: " << captRefrnc.get(CV_CAP_PROP_FRAME_COUNT) << endl;

    cout << "PSNR trigger value " <<
          setiosflags(ios::fixed) << setprecision(3) << psnrTriggerValue << endl;

    Mat frameReference, frameUnderTest;
    double psnrV;
    Scalar mssimV;

    while( true) //Show the image captured in the window and repeat
    {
        captRefrnc >> frameReference;
        captUndTst >> frameUnderTest;

        if( frameReference.empty()  || frameUnderTest.empty())
        {
            cout << " < < <  Game over!  > > > ";
            break;
        }

        ++frameNum;
        cout <<"Frame:" << frameNum;

        ///////////////////////////////// PSNR ////////////////////////////////////////////////////
        psnrV = getPSNR(frameReference,frameUnderTest);                 //get PSNR
        cout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB";

        //////////////////////////////////// MSSIM /////////////////////////////////////////////////
        if (psnrV < psnrTriggerValue)
        {
            mssimV = getMSSIM(frameReference,frameUnderTest);

            cout << " MSSIM: "
                 << "R" << setiosflags(ios::fixed) << setprecision(3) << mssimV.val[2] * 100
                 << "G" << setiosflags(ios::fixed) << setprecision(3) << mssimV.val[1] * 100
                 << "B" << setiosflags(ios::fixed) << setprecision(3) << mssimV.val[0] * 100;
        }

       cout << endl;

        ////////////////////////////////// Show Image /////////////////////////////////////////////
        imshow( WIN_RF, frameReference);
        imshow( WIN_UT, frameUnderTest);

        c = cvWaitKey(delay);
        if (c == 27) break;
    }

    return 0;
}

double getPSNR(const Mat& I1, const Mat& I2)
{
    Mat s1;
    absdiff(I1, I2, s1);       // |I1 - I2|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |I1 - I2|^2

    Scalar s = sum(s1);         // sum elements per channel

    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels

    if( sse <= 1e-10) // for small values return zero
        return 0;
    else
    {
        double  mse =sse /(double)(I1.channels() * I1.total());
        double psnr = 10.0*log10((255*255)/mse);
        return psnr;
    }
}

Scalar getMSSIM( const Mat& i1, const Mat& i2)
{
    const double C1 = 6.5025, C2 = 58.5225;
    /***************************** INITS **********************************/
    int d     = CV_32F;

    Mat I1, I2;
    i1.convertTo(I1, d);           // cannot calculate on one byte large values
    i2.convertTo(I2, d);

    Mat I2_2   = I2.mul(I2);        // I2^2
    Mat I1_2   = I1.mul(I1);        // I1^2
    Mat I1_I2  = I1.mul(I2);        // I1 * I2

    /*************************** END INITS **********************************/

    Mat mu1, mu2;   // PRELIMINARY COMPUTING
    GaussianBlur(I1, mu1, Size(11, 11), 1.5);
    GaussianBlur(I2, mu2, Size(11, 11), 1.5);

    Mat mu1_2   =   mu1.mul(mu1);
    Mat mu2_2   =   mu2.mul(mu2);
    Mat mu1_mu2 =   mu1.mul(mu2);

    Mat sigma1_2, sigma2_2, sigma12;

    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
    sigma1_2 -= mu1_2;

    GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
    sigma2_2 -= mu2_2;

    GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
    sigma12 -= mu1_mu2;

    ///////////////////////////////// FORMULA ////////////////////////////////
    Mat t1, t2, t3;

    t1 = 2 * mu1_mu2 + C1;
    t2 = 2 * sigma12 + C2;
    t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))

    t1 = mu1_2 + mu2_2 + C1;
    t2 = sigma1_2 + sigma2_2 + C2;
    t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))

    Mat ssim_map;
    divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;

    Scalar mssim = mean( ssim_map ); // mssim = average of ssim map
    return mssim;
}

您可以从两个地方启动Visual Studio构建。从IDE(键盘组合:Control-F5)或通过导航到您的构建目录并双击启动应用程序。抓住的是这两个不一样。当您从IDE启动它的当前工作目录是项目目录,否则它是应用程序文件当前的文件夹(通常是您的构建目录)。此外,如果从IDE启动,控制台窗口一旦完成就不会关闭。它会等待你的击键。

当您在代码中打开代码并保存命令时,这一点很重要。您的资源将相对于您的工作目录保存(并查询打开!!!)。这是除非您给出一个完整的,显式的路径作为I / O功能的参数。在上面的代码中,我们打开了OpenCV标志。启动应用程序之前,请确保将映像文件放在当前的工作目录中。修改代码中的图像文件名,以便在其他图像上进行尝试。运行它并且voá:

SuccessVisualStudioWindows

Visual Studio的命令行参数

在我们将来的一些教程中,您将看到程序主输入法将通过给出一个运行时参数。为此,您可以启动命令窗口(在开始菜单中为cmd + Enter),导航到可执行文件并使用参数启动它。所以例如在我的上层项目的情况下,这将是:

D:
CD OpenCV \ MySolutionName \ Release
MySolutionName.exe exampleImage.jpg

在这里,我首先改变了我的驱动器(如果您的项目不在OS本地驱动器上),则导航到我的项目并以示例图像参数启动它。而在Linux系统下,常见的是微软Windows上的控制台窗口,很多人几乎从不使用它。此外,在测试您的应用程序时,一次又一次添加相同的参数,这在某种程度上是一个繁琐的任务。幸运的是,在Visual Studio中有一个菜单可以自动化所有这些:

VisualStudioCommandLineArguments

在这里指定输入的名称,当您从Visual Studio环境启动应用程序时,您将自动参数传递。在下一个介绍性教程中,您将看到对源代码较高的深入解释:加载和显示图像。