OpenCV 图像处理学习手册:6~7

news2024/12/25 12:25:59

原文:Learning Image Processing with OpenCV

协议:CC BY-NC-SA 4.0

译者:飞龙

本文来自【ApacheCN 计算机视觉 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。

当别人说你没有底线的时候,你最好真的没有;当别人说你做过某些事的时候,你也最好真的做过。

六、计算摄影

计算摄影是指使您能够扩展数字摄影的典型功能的技术。 这可能包括硬件附加组件或修改,但主要指基于软件的技术。 这些技术可能会产生“传统”数码相机无法获得的输出图像。 本章介绍了 OpenCV 中用于计算摄影的一些鲜为人知的技术:高动态范围成像,无缝克隆,脱色和非照片级渲染。 这三个位于库的photo模块中。 注意,在前面的章节中已经考虑了该模块内部的其他技术(修复和去噪)。

高动态范围图像

我们处理的典型图像每像素有 8 位(BPP)。 彩色图像还使用 8 位表示每个通道的值,即红色,绿色和蓝色。 这意味着仅使用 256 个不同的强度值。 在数字成像的整个历史中,这个 8 BPP 的限制一直盛行。 但是,很明显,自然界中的光并不只有 256 个不同的水平。 因此,我们应该考虑这种离散化是理想的还是足够的。 例如,已知人眼可以捕获更高的动态范围(最暗和最亮之间的亮度级别数),估计在 1 亿到 1 亿个亮度级别之间。 在只有 256 个光照级别的情况下,有些情况下明亮的光线看起来过度曝光或饱和,而黑暗的场景只是被捕获为黑色。

有些相机可以捕获超过 8 BPP 的图像。 但是,创建高动态范围图像的最常见方法是使用 8 BPP 相机并拍摄具有不同曝光值的图像。 当我们这样做时,动态范围有限的问题显而易见。 例如,考虑下图:

High-dynamic-range images

用六个不同的曝光值拍摄的场景

注意

左上方的图像大部分为黑色,但窗口详细信息可见。 相反,右下角的图像显示了房间的细节,但窗口的细节几乎看不见。

我们可以使用现代智能手机相机以不同的曝光水平拍摄照片。 例如,对于 iPhone 和 iPad,从 iOS 8 开始,使用本机相机应用更改曝光非常容易。 触摸屏幕,将出现一个黄色框,侧面带有一个小太阳。 向上或向下滑动可以更改曝光(请参见以下屏幕截图)。

注意

曝光级别的范围非常大,因此我们可能不得不重复多次滑动手势。

如果您使用的是 iOS 的早期版本,则可以下载相机应用,例如 Camera+,这些应用可让您专注于特定点并更改曝光。

对于 Android,可以在 Google Play 上使用大量相机应用来调整曝光度。 一个例子是 Camera FV-5,它具有免费和付费版本。

提示

如果使用手持设备捕获图像,请确保该设备是静态的。 实际上,您可能会使用三脚架。 否则,具有不同曝光度的图像将无法对齐。 同样,移动的被摄体将不可避免地产生鬼影。 在大多数情况下,低,中和高曝光量的三张图像就足够了。

High-dynamic-range images

使用 iPhone 5S 中的本机摄像头应用进行曝光控制

智能手机和桌子很方便,可以拍摄许多曝光不同的图像。 要创建 HDR 图像,我们需要知道每个捕获图像的曝光(或快门)时间(原因请参见以下部分)。 并非所有应用都允许您手动控制(甚至查看)此功能(iOS 8 本机应用则不允许)。 在撰写本文时,至少有两个免费的应用允许 iOS 版使用:ManuallyManualShot。 在 Android 中,免费的 Camera FV-5 可让您控制和查看曝光时间。 请注意,F/Stop 和 ISO 是控制曝光的其他两个参数。

捕获的图像可以传输到开发计算机,并用于创建 HDR 图像。

注意

从 iOS 7 开始,本机相机应用具有 HDR 模式,可自动快速捕获三幅图像,每幅图像具有不同的曝光度。 这些图像也会自动组合为单个(有时更好)的图像。

创建 HDR 图像

我们如何将多张(例如三张)曝光图像合成为 HDR 图像? 如果我们仅考虑一个通道和一个给定的像素,则必须在较大的输出范围(例如 16 bpp)中将这三个像素值(每个曝光级别一个)映射到单个值。 这种映射并不容易。 首先,我们必须考虑像素强度是传感器辐照度(入射在相机传感器上的光量)的(粗略)度量。 数码相机以非线性方式测量辐照度。 相机具有非线性响应函数,可以将辐照度转换为像素强度值,范围为 0 到 255。 为了将这些值映射到更大的离散值集,我们必须估计摄像机的响应函数(即,响应范围在 0 到 255 之间)。

我们如何估计相机响应函数? 我们从像素本身做到这一点! 响应函数是每个颜色通道的 Sigmoid 曲线,可以根据像素进行估计(如果有 3 个像素曝光,则每个颜色通道的曲线上有 3 个点)。 由于这非常耗时,因此通常选择一组随机像素。

只剩下一件事了。 我们之前曾讨论过估计辐照度和像素强度之间的关系。 我们如何知道辐照度? 传感器的辐照度与曝光时间(或等效地,快门速度)成正比。 这就是我们需要曝光时间的原因!

最后,将 HDR 图像计算为从每次曝光的像素中恢复的辐照度值的加权和。 请注意,此图像无法在范围有限的常规屏幕上显示。

注意

关于高动态范围成像的一本好书是 Reinhard 等人的《高动态范围成像:获取,显示和基于图像的照明》,Morgan Kaufmann Pub。 该书随附 DVD,其中包含不同 HDR 格式的图像。

范例

OpenCV(仅从 3.0 版开始)提供了从一组以不同曝光拍摄的图像中创建 HDR 图像的函数。 甚至还有一个名为hdr_imaging的教程示例,该示例从图像文件中读取图像文件和曝光时间列表,并创建 HDR 图像。

注意

为了运行hdr_imaging教程,您将需要使用列表下载所需的图像文件和文本文件。 您可以从这个页面下载它们。

CalibrateDebevecMergeDebevec类实现 Debevec 的方法来估计相机响应函数并将曝光分别合并为 HDR 图像。createHDR示例之后的向您展示了如何使用这两个类:

#include <opencv2/photo.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int, char** argv)
{
    vector<Mat> images;
    vector<float> times;

    // Load images and exposures...
    Mat img1 = imread("1div66.jpg");
    if (img1.empty())
    {
        cout << "Error! Input image cannot be read...\n";
        return -1;
    }
    Mat img2 = imread("1div32.jpg");
    Mat img3 = imread("1div12.jpg");
    images.push_back(img1);
    images.push_back(img2);
    images.push_back(img3);
    times.push_back((float)1/66);
    times.push_back((float)1/32);
    times.push_back((float)1/12);

    // Estimate camera response...
    Mat response;
 Ptr<CalibrateDebevec> calibrate = createCalibrateDebevec();
 calibrate->process(images, response, times);

    // Show the estimated camera response function...
    cout << response;

    // Create and write the HDR image...
    Mat hdr;
 Ptr<MergeDebevec> merge_debevec = createMergeDebevec();
 merge_debevec->process(images, hdr, times, response);
    imwrite("hdr.hdr", hdr);

    cout << "\nDone. Press any key to exit...\n";
    waitKey(); // Wait for key press
    return 0;
}

该示例使用三个杯子的图像(这些图像以及本书随附的代码均可用)。 图像是使用 ManualShot 拍摄的前面提到的应用,使用的曝光时间为 1/66、1/32 和 1/12 秒; 请参考下图:

Example

示例中用作输入的三个图像

请注意,createCalibrateDebevec方法在 STL 向量中期望图像和曝光时间(STL 是一种有用的常用函数和标准 C++ 中可用的数据结构的库)。 相机响应函数以 256 个实值向量的形式给出。 这表示像素值和辐照度之间的映射。 实际上,它是一个256 x 3的矩阵(三个颜色通道中的每个颜色通道一列)。 下图显示了示例给出的响应:

Example

估计的 RGB 相机响应函数

提示

代码的cout部分以 MATLAB 和 Octave(两种用于数值计算的包)使用的格式显示矩阵。 复制输出中的矩阵并将其粘贴到 MATLAB/Octave 中以进行显示很简单。

生成的 HDR 图像以无损 RGBE 格式存储。 此图像格式使用每个颜色通道一个字节,再加上一个字节作为共享指数。 格式使用与浮点数表示法相同的原理:共享指数允许您表示更大范围的值。 RGBE 图像使用.hdr扩展名。 请注意,由于它是无损图像格式,因此.hdr文件相对较大。 在此示例中,RGB 输入图像分别为 1224 x 1632(每个 100 至 200 KB),而输出.hdr文件占用 5.9 MB。

该示例使用 Debevec 和 Malik 的方法,但是 OpenCV 还基于 Robertson 的方法提供了另一个校准函数。 校准和合并函数均可用,即createCalibrateRobertsonMergeRobertson

注意

有关其他函数及其背后原理的更多信息,请参见这个页面。

最后,请注意示例不会显示结果图像。 HDR 图像无法在常规屏幕中显示,因此我们需要执行另一步,称为色调映射。

色调映射

当要显示高动态范围图像时,信息可能会丢失。 这是由于,因为计算机屏幕的对比度也很有限,而且打印材料通常也限制为 256 色。 当我们具有高动态范围的图像时,有必要将强度映射到一组有限的值。 这称为色调映射。

为了提供逼真的输出,仅将 HDR 图像值缩放到显示设备的缩小范围是不够的。 缩放通常会产生缺乏细节(对比度)的图像,从而消除了原始场景内容。 最终,色调映射算法旨在提供视觉上看起来类似于原始场景的输出(即类似于人类在查看场景时所看到的输出)。 已经提出了各种音调映射算法,并且仍然是广泛研究的问题。 以下代码行可以将色调映射应用于上一个示例中获得的 HDR 图像:

Mat ldr;
Ptr<TonemapDurand> tonemap = createTonemapDurand(2.2f);
tonemap->process(hdr, ldr); // ldr is a floating point image with 
ldr=ldr*255;      //  values in interval [0..1]
imshow("LDR", ldr);

该方法由 Durand 和 Dorsey 于 2002 年提出。构造器实际上接受许多影响输出的参数。 下图显示了输出。 请注意,此图像不一定比三个原始图像中的任何一个都要好:

Tone mapping

音调输出

OpenCV 中提供了其他三种音调映射算法:createTonemapDragocreateTonemapReinhardcreateTonemapMantiuk

可以使用 MATLAB 显示 HDR 图像(RGBE 格式,即扩展名为.hdr的文件)。 它只需要三行代码:

hdr=hdrread('hdr.hdr');
rgb=tonemap(hdr); 
imshow(rgb);

注意

pfstools 是命令行工具的开源套件,用于读取,写入和渲染 HDR 图像。 该套件可以读取.hdr和其他格式,其中包括许多相机校准和色调映射算法。 Luminance HDR 是基于 pfstools 的免费 GUI 软件。

对齐

用多张曝光图像拍摄的场景必须是静态的。 摄像机也必须是静态的。 即使满足两个条件,也建议执行对齐过程。

OpenCV 提供了 G. Ward 在 2003 年提出的图像对齐算法。主要函数createAlignMTB采用定义最大位移的输入参数(实际上,每个尺寸的最大位移以 2 为底的对数)。 在上一示例中估计摄像机响应函数之前,应插入以下几行:

    vector<Mat> images_(images);
    Ptr<AlignMTB> align=createAlignMTB(4);// 4=max 16 pixel shift
    align->process(images_, images);

曝光融合

我们也可以使用相机响应校准(即曝光时间)或中间 HDR 图像,将具有多次曝光的图像组合在一起。 这称为曝光融合。 该方法由 Mertens 等人在 2007 年提出。以下几行执行曝光融合(images是输入图像的 STL 向量;请参见前面的示例):

    Mat fusion;
    Ptr<MergeMertens> merge_mertens = createMergeMertens();
    merge_mertens->process(images, fusion); // fusion is a 
 fusion=fusion*255; // float. point image w. values in [0..1]
 imwrite("fusion.png", fusion);

下图显示了结果:

Exposure fusion

曝光融合

无缝克隆

在无缝克隆中,我们通常要在源图像中剪切一个对象/人并将其插入目标图像。 当然,这可以通过简单地粘贴对象以简单的方式完成。 但是,这不会产生现实的效果。 参见下图,例如,我们想要将图像上半部分的船插入图像下半部分的海中:

Seamless cloning

克隆

从 OpenCV 3 开始,已有无缝克隆函数可用,其结果更为真实。 此函数称为seamlessClone,它使用 Perez 和 Gangnet 在 2003 年提出的方法。以下SeamlessCloning示例向您展示了如何使用它:

#include <opencv2/photo.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int, char** argv)
{
    // Load and show images...
    Mat source = imread("source1.png", IMREAD_COLOR);
    Mat destination = imread("destination1.png", IMREAD_COLOR);
    Mat mask = imread("mask.png", IMREAD_COLOR);
    imshow("source", source);
    imshow("mask", mask);
    imshow("destination", destination);

    Mat result;
    Point p;    // p will be near top right corner
    p.x = (float)2*destination.size().width/3;
    p.y = (float)destination.size().height/4;
    seamlessClone(source, destination, mask, p, result, NORMAL_CLONE);
    imshow("result", result);

    cout << "\nDone. Press any key to exit...\n";
    waitKey(); // Wait for key press
    return 0;
}

这个例子很简单。 seamlessClone函数获取源图像,目标图像和遮罩图像以及目标图像中将插入裁剪对象的点(可以从这个页面)。 请参见下图的结果:

Seamless cloning

无缝克隆

seamlessClone的最后一个参数表示要使用的确切方法(可以使用三种方法产生不同的最终效果)。 另一方面,库提供以下相关函数:

函数效果
colorChange将源图像的三个颜色通道中的每个乘以一个因子,仅在遮罩给定的区域中应用乘法
illuminationChange仅在遮罩指定的区域内更改源图像的照度
textureFlattening仅在遮罩指定的区域中洗掉源图像中的纹理

seamlessClone相反,这三个函数仅接受源图像和遮罩图像。

脱色

脱色是将彩色图像转换为灰度的过程。 有了这个定义,读者可能会问,我们是否已经有了灰度转换? 是的,灰度转换是 OpenCV 和任何图像处理库中的基本例程。 标准转换基于 R,G 和 B 通道的线性组合。 问题在于这种转换可能会产生原始图像中的对比度丢失的图像。 原因是两种不同的颜色(在原始图像中被视为对比度)可能最终被映射到相同的灰度值。 考虑将 A 和 B 这两种颜色转换为灰度。 假设 B 是 R 和 G 通道中 A 的变体:

A = (R, G, B) => G = (R + G + B) / 3
B = (R-x, G + x, B) => G = (R-x + G + x + B) / 3 = (R + G + B) / 3

即使它们被认为是截然不同的,两种颜色 A 和 B 也被映射为相同的灰度值! 以下脱色示例的图像显示如下:

#include <opencv2/photo.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int, char** argv)
{
    // Load and show images...
    Mat source = imread("color_image_3.png", IMREAD_COLOR);
    imshow("source", source);

    // first compute and show standard grayscale conversion...
    Mat grayscale = Mat(source.size(),CV_8UC1);
    cvtColor(source, grayscale, COLOR_BGR2GRAY);
    imshow("grayscale",grayscale);

    // now compute and show decolorization...
    Mat decolorized = Mat(source.size(),CV_8UC1);
    Mat dummy = Mat(source.size(),CV_8UC3);
    decolor(source,decolorized,dummy);
    imshow("decolorized",decolorized);

    cout << "\nDone. Press any key to exit...\n";
    waitKey(); // Wait for key press
    return 0;
}

Decolorization

脱色示例输出

这个例子很简单。 读取图像并显示标准灰度转换的结果后,它使用decolor函数执行脱色。 所使用的图像(color_image_3.png文件)包含在 opencv_extra 存储库中,位于这个页面。

注意

该示例中使用的图像实际上是一个极端的情况。 选择其颜色是为了使标准灰度输出相当均匀。

非真实感渲染

作为photo模块的的一部分,提供了四个函数,这些函数可以转换输入图像,从而产生不真实但仍具有艺术感的输出。 这些函数非常易于使用,OpenCV(npr_demo)中包含一个很好的示例。 为了说明的目的,在这里我们为您提供一个表格,让您掌握每个函数的效果。 看一下下面的fruits.jpg输入图像,包含在 OpenCV 中:

Non-photorealistic rendering

输入参考图像

效果是:

函数影响
edgePreservingFilter平滑是一种方便且经常使用的过滤器。 此函数在保留对象边缘细节的同时执行平滑处理。
Non-photorealistic rendering
detailEnhance增强图像中的细节
Non-photorealistic rendering
pencilSketch输入图像的铅笔状线条图版本
Non-photorealistic rendering
stylization水彩效果
Non-photorealistic rendering

总结

在本章中,您学习了什么是计算摄影以及 OpenCV 3 中可用的相关功能。我们解释了photo模块中最重要的函数,但请注意,在此模块中还考虑了该模块的其他功能(修复和降噪) 前几章。 计算摄影是一个快速发展的领域,与计算机图形学有着紧密的联系。 因此,预计 OpenCV 的此模块将在将来的版本中增加。

下一章将讨论我们尚未考虑的重要方面:时间。 解释的许多功能都需要花费大量时间来计算结果。 下一章将向您展示如何使用现代硬件进行处理。

七、加速图像处理

本章使用通用图形处理单元(GPGPU)或简称为 GPU 进行并行处理来加速图像处理任务。 GPU 本质上是专用于图形处理或浮点运算的协处理器,旨在提高视频游戏和交互式 3D 图形等应用的性能。 在 GPU 中执行图形处理时,CPU 可以专用于其他计算(例如游戏中的人工智能部分)。 每个 GPU 都配备了数百个简单的处理内核,这些内核可对(通常)浮点数的数百个“简单”数学运算进行大规模并行执行。

CPU 似乎已达到其速度和热功率极限。 用多个 CPU 构建计算机已成为一个复杂的问题。 这就是 GPU 发挥作用的地方。 GPU 处理是一种新的计算范例,它使用 GPU 来提高计算性能。 GPU 最初实现了某些称为图形基元的并行操作,这些并行操作已针对图形处理进行了优化。 抗锯齿是 3D 图形处理最常见的原语之一,它使图形的边缘具有更逼真的外观。 其他图元是矩形,三角形,圆形和弧形的图形。 GPU 当前包含数百个通用处理功能,它们的功能远远超过渲染图形。 特别是,它们在可以并行执行的任务中非常有价值,许多计算机视觉算法就是这种情况。

OpenCV 库包括对 OpenCL 和 CUDA GPU 架构的支持。 CUDA 实现了许多算法。 但是,它仅适用于 NVIDIA 图形卡。 CUDA 是由 NVIDIA 创建并由其产生的 GPU 实现的并行计算平台和编程模型。 本章重点介绍 OpenCL 架构,因为它受到更多设备的支持,甚至包括在某些 NVIDIA 图形卡中。

开放计算语言OpenCL)是框架,可编写可在连接到主机处理器(CPU)的 CPU 或 GPU 上执行的程序。 它定义了一种类似于 C 的语言来编写称为内核的函数,这些函数在计算设备上执行。 使用 OpenCL,内核可以在与 CPU 或 GPU 并行的所有或许多单个处理元素(PE)上运行。

此外,OpenCL 定义了应用编程接口API),该接口允许在主机(CPU)上运行的程序在计算机设备上启动内核并管理它们的设备存储器,(至少在概念上)与主机存储器分开。 OpenCL 程序旨在在运行时进行编译,以便使用 OpenCL 的应用可在各种主机设备的实现之间移植。 OpenCL 还是非盈利技术联盟 Khronos Group 维护的开放标准。

OpenCV 包含一组类和函数,这些类和函数使用 OpenCL 来实现和加速 OpenCV 功能。 OpenCV 当前提供一个透明的 API,该 API 可以将其原始 API 与 OpenCL 加速的编程统一起来。 因此,您只需要编写一次代码。 有一个新的统一数据结构(UMat),在需要且可能时处理向 GPU 的数据传输。

OpenCV 中对 OpenCL 的支持是为了易于使用而设计的,不需要任何 OpenCL 知识。 在最低程度上,它可以看作是一组加速,在使用现代 CPU 和 GPU 设备时可以利用强大的计算能力。

要正确运行 OpenCL 程序,OpenCL 运行时应由设备供应商提供,通常以设备驱动程序的形式提供。 另外,要将 OpenCV 与 OpenCL 一起使用,需要兼容的 SDK。 当前,有五个可用的 OpenCL SDK:

  • AMD APP SDK:此 SDK 在 CPU 和 GPU(例如 X86 + SSE2(或更高)CPU 和 AMD Fusion,AMD Radeon,AMD Mobility 和 ATI FirePro GPU)上支持 OpenCL。
  • Intel SDK:此 SDK 在 Intel Core 处理器和 Intel HD GPU(例如 Intel + SSE4.1,SSE4.2 或 AVX,Intel Core i7,i5 和 i3( 第 1 代,第 2 代和第 3 代),Intel HD Graphics,Intel Core 2 Solo(Duo Quad 和 Extreme)和 Intel Xeon CPU。
  • IBM OpenCL 开发套件:此 SDK 在 AMD 服务器(例如 IBM Power,IBM PERCS 和 IBM BladeCenter)上支持 OpenCL。
  • IBM OpenCL 通用运行时:此 SDK 在 CPU 和 GPU(例如 X86 + SSE2(或更高版本) CPU 和 AMD Fusion and Raedon,NVIDIA Ion,NVIDIA GeForce 和 NVIDIA)上支持 OpenCV Quadro GPU。
  • Nvidia OpenCL 驱动程序和工具:此 SDK 在某些 Nvidia 图形设备(例如 NVIDIA Tesla,NVIDIA GeForce,NVIDIA Ion 和 NVIDIA Quadro GPU)上支持 OpenCL。

安装了 OpenCL 的 OpenCV

第 1 章,“处理图像和视频文件”中已经介绍了安装步骤,还需要一些其他步骤来包含 OpenCL。 下节介绍了新需要的软件。

在 Windows 上使用 OpenCL 编译和安装 OpenCV 有一些新要求:

当满足之前的要求时,您可以使用 CMake 生成新的构建配置。 该过程与第一章中介绍的典型安装在某些方面有所不同。 差异在此列表中说明:

  • 为项目选择生成器时,可以选择与计算机中已安装环境相对应的编译器版本。 本章使用 MinGW 使用 OpenCL 编译 OpenCV,然后选择MinGW Makefiles选项,并指定本机编译器。 以下屏幕截图显示了此选择:

    OpenCV with the OpenCL installation

    CMake 选择生成器项目

  • 以下屏幕截图中显示的选项是构建带有 OpenCL 项目的 OpenCV 所必需的。 必须启用WITH_OPENCLWITH_OPENCLAMDBLASWITH_OPENCLAMDFFT选项。 必须在CLAMDBLAS_INCLUDE_DIRCLAMDBLAS_ROOT_DIRCLAMDFFT_INCLUDE_DIRCLAMDFFT_ROOT_DIR上引入 BLAS 和 FFT 路径。 此外,如第 1 章“处理图像和视频文件”中所示,您将需要启用WITH_QT并禁用WITH_IPP选项。 也建议启用BUILD_EXAMPLES。 以下屏幕截图显示了在构建配置中选择的主要选项:

    OpenCV with the OpenCL installation

    CMake 选择主要选项

最后,要使用 OpenCL 项目构建 OpenCV,必须编译先前生成的 CMake 项目。 该项目是为 MinGW 生成的,因此,需要 MinGW 编译器来构建此项目。 首先,使用 Windows 控制台选择[opencv_build]/文件夹,然后执行以下操作:

./mingw32-make.exe -j 4 install

-j 4参数是我们要用于编译并行化的系统核心 CPU 的数量。

现在可以使用带有 OpenCL 项目的 OpenCV。 新二进制文件的路径必须添加到系统路径,在这种情况下为[opencv_build]/install/x64/mingw/bin

注意

不要忘记从路径环境变量中删除旧的 OpenCV 二进制文件。

使用 OpenCL 安装 OpenCV 的快速方法

可以通过以下步骤总结安装过程:

  1. 下载并安装 AMD APP SDK,该软件可从这个页面获得。
  2. 下载并安装 BLAS 和 FFT AMD,它们可从这个页面。
  3. 使用 CMake 配置 OpenCV 构建。 启用 WITH_OPENCLWITH_OPENCLAMDBLAS,WITH_QTBuild_EXAMPLESWITH_OPENCLAMDFFT 选项。 禁用 WITH_IPP 选项。 最后,介绍 CLAMDBLAS_INCLUDE_DIRCLAMDBLAS_ROOT_DIRCLAMDFFT_INCLUDE_DIRCLAMDFFT_ROOT_DIR 上的 BLAS 和 FFT 路径。
  4. mingw32-make.exe编译 OpenCV 项目。
  5. 最后,修改路径环境变量以更新 OpenCV bin目录(例如[opencv_build]/install/x64/mingw/bin)。

检查 GPU 使用情况

在 Windows 平台上使用 GPU 时,没有应用可以测量其使用情况。 使用 GPU 的原因有两个:

  • 可以知道您是否正确使用了 GPU
  • 您可以监控 GPU 使用率

为此,市场上有一些应用。 本章使用 AMD 系统监视器检查 GPU 的使用情况。 此应用监视 CPU,内存 RAM 和 GPU 的使用情况。 请参考以下屏幕截图:

Check the GPU usage

AMD 系统监视器可监视 CPU,GPU 和内存 RAM 的使用情况

注意

可以从这个页面下载 Microsoft System Monitor (32 或 64 位)。

加速您自己的功能

在本节中,有使用 OpenCV 和 OpenCL 的三个示例。 第一个示例使您可以检查已安装的 SDK 是否可用,并获取有关支持 OpenCL 的计算设备的有用信息。 第二个示例分别显示使用 CPU 和 GPU 编程的同一程序的两个版本。 最后一个示例是检测和标记人脸的完整程序。 另外,执行计算比较。

检查您的 OpenCL

以下是一个简单的程序,用于检查您的 SDK 和可用的计算设备。 该示例是,称为checkOpenCL。它允许您使用 OpenCV 的 OCL 模块显示计算机设备:

#include <opencv2/opencv.hpp>
#include <opencv2/core/ocl.hpp>

using namespace std;
using namespace cv;
using namespace cv::ocl;

int main()
{
vector<ocl::PlatformInfo> info;
 getPlatfomsInfo(info);
PlatformInfo sdk = info.at(0);

    if (sdk.deviceNumber()<1)
        return -1;

    cout << "******SDK*******" << endl;
    cout << "Name: " <<sdk.name()<< endl;
cout << "Vendor: " <<sdk.vendor()<< endl;
cout << "Version: " <<sdk.version()<< endl;
    cout << "Number of devices: " <<sdk.deviceNumber()<< endl;

    for (int i=0; i<sdk.deviceNumber(); i++){
Device device;
 sdk.getDevice(device, i);
          cout << "\n\n*********************\n Device " << i+1 << endl;

        cout << "Vendor ID: " <<device.vendorID()<< endl;
        cout << "Vendor name: " <<device.vendorName()<< endl;
        cout << "Name: " <<device.name()<< endl;
        cout << "Driver version: " <<device.driverVersion()<< endl;
        if (device.isAMD()) cout << "Is an AMD device" << endl;
        if (device.isIntel()) cout << "Is a Intel device" << endl;
        cout << "Global Memory size: " <<device.globalMemSize()<< endl;
        cout << "Memory cache size: " <<device.globalMemCacheSize()<< endl;
        cout << "Memory cache type: " <<device.globalMemCacheType()<< endl;
        cout << "Local Memory size: " <<device.localMemSize()<< endl;
        cout << "Local Memory type: " <<device.localMemType()<< endl;
        cout << "Max Clock frequency: " <<device.maxClockFrequency()<< endl;
    }

    return 0;
}

代码说明

本示例显示安装的 SDK 和与 OpenCL 兼容的可用计算设备。 首先,包含core/ocl.hpp标头,并声明cv::ocl命名空间。

使用getPlatfomsInfo(info)方法获取有关计算机中可用 SDK 的信息。 该信息存储在vector<ocl::PlatformInfo> info向量中,并通过PlatformInfo sdk = info.at(0)选择。 然后,将显示有关您的 SDK 的主要信息,例如名称,供应商,SDK 版本以及与 OpenCL 兼容的计算设备的数量。

最后,对于每个兼容设备,其信息都是通过sdk.getDevice(device, i)方法获得的。 现在可以显示有关每个计算设备的不同信息,例如供应商 ID,供应商名称,驱动程序版本,全局内存大小,内存缓存大小等。

下面的屏幕截图显示了该示例对所用计算机的结果:

The code explanation

有关使用的 SDK 和兼容的计算设备的信息

您的第一个基于 GPU 的程序

在下面的代码中,显示了同一程序的两个版本:一个仅使用 CPU(本机)执行计算,另一个使用 GPU(带有 OpenCL)。 这两个示例分别称为calculateEdgesCPUcalculateEdgesGPU,使您可以观察 CPU 和 GPU 版本之间的差异。

首先显示计算边缘 CPU 示例:

#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(int argc, char * argv[])
{
    if (argc < 2)
    {
        cout << "./calculateEdgesCPU <image>" << endl;
        return -1;
    }

Mat cpuFrame = imread(argv[1]);
    Mat cpuBW, cpuBlur, cpuEdges;

    namedWindow("Canny Edges CPU",1);

cvtColor(cpuFrame, cpuBW, COLOR_BGR2GRAY);
 GaussianBlur(cpuBW, cpuBlur, Size(1,1), 1.5, 1.5);
 Canny(cpuBlur, cpuEdges, 50, 100, 3);

imshow("Canny Edges CPU", cpuEdges);
    waitKey();

    return 0;
}

现在,显示计算边缘 GPU 示例:

#include "opencv2/opencv.hpp"
#include "opencv2/core/ocl.hpp"

using namespace std;
using namespace cv;
using namespace cv::ocl;

int main(int argc, char * argv[])
{
    if (argc < 2)
    {
        cout << "./calculateEdgesGPU <image>" << endl;
        return -1;
    }

setUseOpenCL(true);

Mat cpuFrame = imread(argv[1]);
UMat gpuFrame, gpuBW, gpuBlur, gpuEdges;

cpuFrame.copyTo(gpuFrame);

    namedWindow("Canny Edges GPU",1);

    cvtColor(gpuFrame, gpuBW, COLOR_BGR2GRAY);
    GaussianBlur(gpuBW, gpuBlur, Size(1,1), 1.5, 1.5);
    Canny(gpuBlur, gpuEdges, 50, 100, 3);

imshow("Canny Edges GPU", gpuEdges);
    waitKey();

    return 0;
}

代码说明

这两个示例获得相同的结果,如以下屏幕截图所示。 他们从标准命令行输入参数读取图像。 然后,将图像转换为灰度,并应用高斯模糊和 Canny 过滤器功能。

在第二个示例中,使用 GPU 需要一些区别。 首先,必须使用setUseOpenCL(true)方法激活 OpenCL。 其次,统一矩阵UMat)用于在 GPU(UMat gpuFrame, gpuBW, gpuBlur, gpuEdges)中分配内存。 第三,使用cpuFrame.copyTo(gpuFrame)方法将输入图像从 RAM 复制到 GPU 内存。 现在,使用这些功能时,如果它们具有 OpenCL 实现,则这些功能将在 GPU 上执行。 如果其中一些功能没有 OpenCL 实现,则正常功能将在 CPU 上执行。 在此示例中,使用 GPU 编程(第二示例)的时间要好 10 倍:

The code explanation

前两个示例的结果

实时

GPU 处理的主要优点之一是以更快的方式执行计算。 速度的提高使您可以在实时应用中执行繁重的计算算法,例如立体视觉,行人检测,光流或人脸检测。 以下detectFaces示例向您展示了一种用于检测摄像机面部的应用。 此示例还允许您在 CPUGPU 处理之间进行选择,以比较计算时间。

在 OpenCV 示例([opencv_source_code]/samples/cpp/facedetect.cpp)中,可以找到相关的人脸检测器示例。 对于以下detectFaces示例,detectFace.pro项目需要以下库:-lopencv_core300-opencv_imgproc300-lopencv_highgui300-lopencv_videoio300lopencv_objdetct300

detectFaces示例使用 OpenCV 的ocl模块:

#include <opencv2/core/core.hpp>
#include <opencv2/core/ocl.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;
using namespace cv::ocl;

int main(int argc, char * argv[])
{
    // 1- Set the initial parameters
    // Vector to store the faces
    vector<Rect> faces;
    CascadeClassifier face_cascade;
String face_cascade_name = argv[2];
    int face_size = 30;
    double scale_factor = 1.1;
    int min_neighbours = 2;

VideoCapture cap(0);
UMat frame, frameGray;
    bool finish = false;

    // 2- Load the file xml to use the classifier
    if (!face_cascade.load(face_cascade_name))
    {
        cout << "Cannot load the face xml!" << endl;
        return -1;
    }

    namedWindow("Video Capture");

    // 3- Select between the CPU or GPU processing
    if (argc < 2)
    {
          cout << "./detectFaces [CPU/GPU | C/G]" << endl;
          cout << "Trying to use GPU..." << endl;
setUseOpenCL(true);
    }
    else
    {
        cout << "./detectFaces trying to use " << argv[1] << endl;
        if(argv[1][0] == 'C')
            // Trying to use the CPU processing
            setUseOpenCL(false);
        else
            // Trying to use the GPU processing
            setUseOpenCL(true);
    }

    Rect r;
 double start_time, finish_time, start_total_time,       finish_total_time;
    int counter = 0;

    // 4- Detect the faces for each image capture
    start_total_time = getTickCount();
    while (!finish)
    {
        start_time = getTickCount();
cap >> frame;
        if (frame.empty())
        {
            cout << "No capture frame --> finish" << endl;
            break;
        }

cvtColor(frame, frameGray, COLOR_BGR2GRAY);
equalizeHist(frameGray,frameGray);

        // Detect the faces
face_cascade.detectMultiScale(frameGray, faces, scale_factor, min_neighbours, 0|CASCADE_SCALE_IMAGE, Size(face_size,face_size));

        // For each detected face
        for (int f = 0; f <faces.size(); f++)
        {
            r = faces[f];
            // Draw a rectangle over the face
rectangle(frame, Point(r.x, r.y), Point(r.x + r.width, r.y + r.height), Scalar(0,255,0), 3);
        }

        // Show the results
imshow("Video Capture",frame);

        // Calculate the time processing
        finish_time = getTickCount();
cout << "Time per frame: " << (finish_time - start_time)/getTickFrequency() << " secs" << endl;

        counter++;

        // Press Esc key to finish
        if(waitKey(1) == 27) finish = true;
    }

    finish_total_time = getTickCount();
cout << "Average time per frame: " << ((finish_total_time - start_total_time)/getTickFrequency())/counter << " secs" << endl;

    return 0;
}

代码说明

第一步,设置初始参数,例如使用分类器检测面部的 xml 文件(String face_cascade_name argv[2]),每个检测到的面部的最小尺寸(face_size=30),比例因子(scale_factor = 1.1 ),以及在真正例和假正面检测之间进行权衡的最小邻居数(min_neighbours = 2)。 您还可以看到 CPU 和 GPU 源代码之间更重要的区别。 您只需要使用统一矩阵UMat frame, frameGray)。

注意

[opencv_source_code]/data/haarcascades/文件夹中还有其他可用的 xml 文件,用于检测不同的身体部位,例如眼睛,下半身,微笑等。

第二步,使用前面的 xml 文件创建检测器以检测面部。 该检测器基于基于 Haar 特征的分类器,这是 Paul Viola 和 Michael Jones 提出的一种有效的对象检测方法。 该分类器具有高精度的人脸检测。 此步骤使用face_cascade.load( face_cascade_name)方法加载 xml 文件。

注意

您可以在这个页面上找到有关 Paul Viola 和 Michael Jones 方法的更多详细信息。

第三步,您可以在 CPU 或 GPU 处理(分别为setUseOpenCL(false)setUseOpenCL(true))之间进行选择。 本示例使用标准命令行输入参数(argv[1])进行选择。 用户可以从 Windows 控制台执行以下操作,以分别在 CPU 或 GPU 处理以及分类器路径之间进行选择:

<bin_dir>/detectFaces CPU pathClassifier
<bin_dir>/detectFaces GPU pathClassifier

如果用户未引入输入参数,则使用 GPU 处理。

第四步为从摄像机捕获的每个图像检测面部。 在此之前,每个捕获的图像都将转换为灰度(cvtColor(frame, frameGray, COLOR_BGR2GRAY))并对其直方图进行均衡(equalizeHist(frameGray, frameGray))。 然后,使用创建的人脸检测器,使用face_cascade.detectMultiScale(frameGray, faces, scale_factor, min_neighbours, 0|CASCADE_SCALE_IMAGE, Size(face_size,face_size))多尺度检测方法在当前帧中搜索不同的面部。 最后,在每个检测到的面部上绘制一个绿色矩形,然后将其显示。 以下屏幕截图显示了此示例运行的屏幕截图:

The code explanation

前面的例子检测人脸

性能

在前面的示例中,计算了计算时间以比较 CPU 和 GPU 处理。 获得每帧的平均处理时间。

选择 GPU 编程的一大优势是性能。 因此,前面的示例计算时间测量值,以比较相对于 CPU 版本获得的加速比。 时间使用getTickCount()方法存储在程序的开头。 之后,在程序结束时,再次使用相同的函数来估计时间。 存储计数器以也知道迭代次数。 最后,计算每帧的平均处理时间。 前面的示例使用 GPU 时每帧的平均处理时间为 0.057 秒(或 17.5 FPS),而使用 CPU 的相同示例时,每帧的平均处理时间为每帧 0.335 秒(或 2.9 FPS)。 总之,速度增量为6x。 此增量非常重要,尤其是当您只需要更改几行代码时。 但是,有可能实现更高的速度增加速率,这与问题甚至内核的设计有关。

总结

在本章中,您学习了如何在计算机上安装带有 OpenCL 的 OpenCV 以及如何使用与 OpenCL 兼容的最新 OpenCV 版本的计算机设备开发应用。

第一部分说明 OpenCL 是什么以及可用的 SDK。 请记住,取决于您的计算设备,您将需要特定的 SDK 才能与 OpenCL 一起正常使用。 在第二部分中,说明了使用 OpenCL 安装 OpenCV 的安装过程,并使用了 AMD APP SDK。 在上一节中,有三个使用 GPU 编程的示例(第二个示例也具有 CPU 版本以便进行比较)。 此外,在最后一节中,在 CPU 和 GPU 处理之间进行了计算比较,显示 GPU 比 CPU 版本快六倍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/439299.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

javascript之数组

创建 有以下几种方式 1.构造函数 在使用构造函数时&#xff0c;可以不带new 创建空数组 let color new Array() 或者let color Array() 创建指定个数元素的数组 let color new Array(2) 或者let color Array(2) 创建指定元素的数组 let color new Array("bl…

atio函数和宏offset的介绍

目录 前言atoi函数宏offsetof总结 前言 本章带大家一起认识一些在我们C语言标准库中的函数 atoi函数 int atio(const char* str);头文件&#xff1a; #include<stdlib.h>参数&#xff1a; str指向常量字符串起始位置的指针 函数介绍&#xff1a; ①解析C语言字符串str,…

2023软件测试工程师必备技能?要卷,谁还不会了......

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 软件测试岗位是怎…

FPGA基于Tri Mode Ethernet MAC实现UDP通信 提供3套工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、米联客UDP协议栈4、详细设计方案5、Tri Mode Ethernet MAC的使用6、vivado工程1详解7、vivado工程2详解8、vivado工程3详解9、上板调试验证并演示10、福利&#xff1a;工程代码的获取 1、前言 目前网上的fpga实现udp基本生态如下&#x…

Spring Security实战(五)—— 密码加密

一、密码加密的演进 MD5 (Message-Digest Algorithm 5) 和 SHA (Secure Hash Algorithm) 是两种常见的消息摘要算法&#xff0c;它们都被用于加密和数据完整性验证等领域。 MD5 是一种 128 位的哈希函数&#xff0c;常用于数据完整性校验和数字签名等方面。它将任意长度的信息…

WebServer项目(四)->(基于Proactor的c++)Web服务器简介及简单实现

基于Proactor的cWeb服务器项目 WebServer项目(四)-&#xff1e;(基于Proactor的c)Web服务器简介及简单实现1.Web Server&#xff08;网页服务器&#xff09;2.HTTP协议(应用层的协议)3.HTTP 请求报文格式4.HTTP响应报文格式5.HTTP请求方法6.HTTP状态码7.服务器编程基本框架8.两…

分布式搜索技术elasticsearch概念篇

文章目录 一、分布式搜索技术二、elasticsearch2.1 初识elasticsearch2.2 正向索引和倒排索引2.2.1 介绍2.2.2 优缺点 2.3 elasticsearch和mysql的对比 一、分布式搜索技术 分布式搜索技术是一种基于分布式计算的搜索引擎技术&#xff0c;它使用多台计算机协同工作来处理大规模…

ElementUI登陆表单中常用的标签属性

ElementUI官网 为登陆框添加一个边角弧度 <style> .className{/*设置div边边框角的弧度*/border-radius: 10px; } </style><el–input>标签常用属性 <!--使用prefix属性添加一个前缀图标--> <el-input prefix-icon"el-icon-user-solid"&g…

深度学习第J6周:ResNeXt-50实战解析

目录 一、模型结构介绍 二、前期准备 三、模型 三、训练运行 3.1训练 3.2指定图片进行预测 &#x1f368; 本文为[&#x1f517;365天深度学习训练营]内部限免文章&#xff08;版权归 *K同学啊* 所有&#xff09; &#x1f356; 作者&#xff1a;[K同学啊] &#x1f4cc; …

大数据技术之集群数据迁移

在大数据集群数据迁移的项目中涉及到很多技术细节&#xff0c;本博客记录了迁移的大致的操作步骤。 迁移借用Hadoop自带的插件&#xff1a;distcp。 一、Hadoop集群数据迁移 **DistCp&#xff08;分布式拷贝&#xff09;**是用于大规模集群内部和集群之间拷贝的工具。它使用M…

Meson构建系统的使用

一、前言 Meson 是用于自动化构建的自由软件&#xff0c;使用Python语言编写&#xff0c;在 Apache 许可证 2.0版本下发布&#xff0c;主要目标是为了让开发者节约用于配置构建系统的时间。 特点如下&#xff1a; 多平台支持&#xff0c;包括 GNU/Linux、Windows、MacOS、GCC、…

A_A01_008 STM32F103系列标准库移植经验分享

A_A01_008 STM32F103系列标准库移植经验分享 一、所需材料二、移植步骤三、注意事项四、参考资料与友情链接 一、所需材料 1.MDK开发环境 此处版本V5.15 2.相关启动文件 此处用野火点灯例程 因为启动文件完整 方便更换 其它工程没有的可以直接复制这些启动文件过去 3.相关开…

Java版工程管理系统源代码-软件自主研发,工程行业适用

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…

8年测开年薪30W,为什么从开发转型为测试?谈谈这些年的心路历程……

谈谈我的以前&#xff0c;从毕业以来从事过两个多月的Oracle开发后转型为软件测试&#xff0c;到现在已近过去8年成长为一个测试开发工程师&#xff0c;总结一下之间的心路历程&#xff0c;希望能给徘徊在开发和测试之前的同学一点小小参考。 一、测试之路伏笔 上学偷懒&#…

EGO-Link FPGA智慧教育社区介绍:用 leetcode 的方式练习 verilog 语言

文章目录 介绍FPGA 语法例1&#xff1a;P1203 1输入1输出例2&#xff1a;P1204 3输入4输出例3&#xff1a;P1207 P1208 P1205 与或非门例4&#xff1a;P1200 半加器例5&#xff1a;P1201 4位二进制转余3循环码例6&#xff1a;P1215 2选1多路选择器例7&#xff1a;P1236 D触发器…

【C语言】浅涉结构体(声明、定义、类型、定义及初始化、成员访问及传参)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 结构体的声明 1.1 结构体的基础知识 1.2 结构的声明 1.3 结构成员的类型 1.4 结构体变量的定义和初始化 2. 结构体成员的访问 3. 结构体传参 1. 结构体的声明 1.1 结构体的基础知识 结构是一些值的集合&…

探索Apache Hudi核心概念 (4) - Clustering

Clustering是Hudi在0.7.0版本引入的一项特性&#xff0c;用于优化文件布局&#xff0c;提升读写性能&#xff0c;现在它已经成为Hudi的一项重要性能优化手段。本文我们会通过Notebook介绍并演示Clustering的运行机制&#xff0c;帮助您理解其工作原理和相关配置。 1. 运行 Not…

RocketMQ 多级存储设计与实现

作者&#xff1a;张森泽 随着 RocketMQ 5.1.0 的正式发布&#xff0c;多级存储作为 RocketMQ 一个新的独立模块到达了 Technical Preview 里程碑&#xff1a;允许用户将消息从本地磁盘卸载到其他更便宜的存储介质&#xff0c;可以用较低的成本延长消息保留时间。本文详细介绍 …

记录贴:EasyPoi word导出问题一览

项目场景&#xff1a; EasyPoi word导出 问题描述1 easypoi 模板导出 我直接在map的value输入空格或"",出来的是{{,两个左花括号,咋解决 解决方案&#xff1a; exportMap.put("key", "\u00A0"); //空格前端效果&#xff1a; 其他无效解决方案…

Redis安装配置操作记录

Redis 官网&#xff1a;https://redis.io/ 中文文档&#xff1a;https://www.redis.com.cn/documentation.html 在线命令参考&#xff1a;http://doc.redisfans.com 一&#xff0c;Redis下载安装与配置 下载网站&#xff0c;可下载安装包然后安装或可使用brew来安装Redis&#…