opencv_cuda版本+cuda编程学习(一)图像显示

首先来看看上一节的代码,因为是并没有完整的学过opencv,所以一些opencv的基础我也会查清楚。

常用头文件讲解:

首先是头文件:

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include<cuda.h>
#include <opencv2/core/cuda.hpp>
#include<opencv2\opencv.hpp>
#include<opencv.hpp>
#include <stdio.h>
#include<iostream>

1.#include "cuda_runtime.h"   

"CUDA Runtime API" 的头文件通常包括 cuda_runtime.h,它是 CUDA 编程中的一个重要头文件,用于包含 CUDA Runtime API 函数的声明和定义。这个头文件提供了访问 CUDA 运行时 API 的接口,允许开发人员在代码中调用 CUDA 函数以与 GPU 进行交互

然后,你可以在你的程序中使用 CUDA 运行时 API 中定义的函数来管理设备内存(cudaMalloc,cudaFree)、启动 GPU 核函数(kernel<<<>>>)、进行数据传输(cudaMemcpy)等操作。

2.#include "device_launch_parameters.h"

这个头文件定义了与 CUDA 核函数启动相关的一些重要宏和结构,用于配置核函数的启动参数。它包括了一些常用的启动参数,如网格大小(grid_size)、块大小(block_size)等。

在 CUDA 编程中,你通常会使用这个头文件来设置核函数的启动参数,以确定如何在 GPU 上启动核函数。

3.#include<cuda.h>

#include <cuda.h> 是 CUDA 编程中用于包含 CUDA Runtime API 的头文件之一。它提供了对 CUDA 运行时 API 函数的声明和定义,允许你在程序中调用这些函数来与 GPU 进行交互。

这个头文件会为你提供对 CUDA 运行时 API 中的各种功能的访问,如设备管理、内存管理、数据传输和核函数启动等。

貌似这个头文件和"cuda_runtime.h"   差不多。

来看看gpt怎么说:

#include "cuda_runtime.h":这个头文件是 cuda.h 的子集,它通常是 cuda.h 的包装器,用于提供对核函数启动相关的功能的额外支持。

#include <cuda.h> 包含了完整的 CUDA 运行时 API,而 #include "cuda_runtime.h" 是一个子集,它专注于核函数启动的支持。通常,对于大多数 CUDA 编程任务,只需要包含 #include <cuda.h> 即可。如果你需要更高级的核函数启动控制,可以选择包含 #include "cuda_runtime.h" 来访问与核函数启动相关的宏和结构。

那这么看来,device_launch_parameters.h"这个头文件也是cuda.h下的一个子集。

4.#include <opencv2/core/cuda.hpp>

#include <opencv2/core/cuda.hpp> 头文件是 OpenCV(Open Source Computer Vision Library)的 CUDA 模块中的头文件之一。OpenCV 是一个用于计算机视觉和图像处理的流行开源库,而 CUDA 模块允许你利用 NVIDIA GPU 的计算能力来加速图像处理和计算机视觉任务。

这个头文件包含了 CUDA 模块的一些功能和类,允许你在 GPU 上执行图像处理和计算机视觉操作。使用 CUDA 模块可以大幅提高处理图像和视频的性能,特别是在需要处理大型图像或进行复杂的计算机视觉任务时。

#include <opencv2/core/cuda.hpp>

int main() {
    cv::Mat srcImage = cv::imread("image.jpg", cv::IMREAD_COLOR);

    // 将图像上传到 GPU 内存
    cv::cuda::GpuMat gpuImage;
    gpuImage.upload(srcImage);

    // 在 GPU 上执行图像处理操作
    cv::cuda::cvtColor(gpuImage, gpuImage, cv::COLOR_BGR2GRAY);

    // 下载 GPU 上处理后的图像
    cv::Mat resultImage;
    gpuImage.download(resultImage);

    // 在结果上执行其他操作...

    return 0;
}

5.#include<opencv2\opencv.hpp>

  • 这是常用的方式,它包含了 OpenCV 的核心模块,提供了 OpenCV 中的基本功能,如图像处理、图像加载和保存、绘图等。
  • 这个头文件包含了 OpenCV 库的核心定义和类,以及其他模块的一些常见定义。
  • 在大多数情况下,如果你只需要基本的图像处理功能,#include<opencv2/opencv.hpp> 就足够了。

6.

#include<opencv.hpp>

  • 这个头文件通常不建议使用,因为它不提供与 OpenCV 的所有功能和模块的访问。
  • #include<opencv.hpp> 是一种缩减版本的头文件,包括了一些核心定义,但可能缺少许多其他模块的定义。
  • 如果你使用 #include<opencv.hpp>,你可能会在编写代码时遇到缺失某些功能的问题。

一般来说,推荐使用 #include<opencv2/opencv.hpp>,因为它提供了 OpenCV 中的核心功能和模块,而且在 OpenCV 的各个版本中都是一致的。如果你需要使用特定的模块,例如图形用户界面模块或机器学习模块,可以根据需要包含相应的头文件,例如 #include<opencv2/highgui/highgui.hpp>#include<opencv2/ml/ml.hpp 等。

命名空间:

using namespace std;
using namespace cv;
using namespace cuda;

这3个分别命名了,C++标准库的命名空间,std,OpenCV库的命名空间,cv,CUDA库的命名空间cuda,方便执行调用函数。但要小心使用,命名空间不能使用太多,因为在不同命名空间下可能存在函数名冲突。所以尽量引入特定的类,而不是整个命名空间。

模板核函数:

template<int nthreads>
__global__ void compute_kernel(int height, int width, const PtrStepb img, PtrStepb dst) {

	const int x = blockIdx.x * blockDim.x + threadIdx.x;
	const int y = blockIdx.y * blockDim.y + threadIdx.y;

	const uchar* src_y = (const uchar*)(img + y * img.step);
	uchar* dst_y = (uchar*)(dst + y * dst.step);
	if (x < width && y < height) {
		dst_y[3 * x] = src_y[3 * x];
		dst_y[3 * x + 1] = src_y[3 * x + 1];
		dst_y[3 * x + 2] = src_y[3 * x + 2];   //三通道
	}


}

template<int nthreads>:这是模板参数声明,它允许你在编译时为 nthreads 提供不同的整数值,以生成多个不同的函数版本。(这么看来模板函数的模板类型T还能是一个数)

__global__:这是CUDA的修饰符,它表示这是一个在GPU上执行的核函数。这个函数将在GPU上并行执行。

PtrStepb 是OpenCV中的一个用于处理图像数据的类,通常用于低级图像数据访问。它是OpenCV中的一个封装类,允许你以指针方式访问图像数据,同时提供了图像的宽度和步幅信息。这对于执行图像处理和计算非常有用。

  1. 提供对图像数据的指针访问:你可以使用 PtrStepb 对象的指针来访问图像数据,这使得你可以执行各种图像处理操作。

  2. 存储图像的宽度和步幅信息:PtrStepb 会存储有关图像的宽度和步幅(stride)的信息。步幅是指图像数据中每行之间的字节数差异,这对于访问多通道或不连续存储图像数据非常有用。

  3. 通常与OpenCV的低级图像处理函数一起使用:PtrStepb 类通常与OpenCV中的低级图像处理函数一起使用,以便更灵活地访问和处理图像数据。

const PtrStepb imgPtrStepb dst:这些参数是图像数据的指针或引用。const 表示 img 是只读的,而 dst 是可写的。

模板参数 nthreads 可以用于控制并行度,允许你在不同情况下使用不同数量的线程。根据实际应用,你需要在核函数中编写适当的CUDA代码以处理图像数据。

	const int x = blockIdx.x * blockDim.x + threadIdx.x;
	const int y = blockIdx.y * blockDim.y + threadIdx.y;

这两个如果看了之前的CUDA编程的文章的话,那么可以知道。

const int x = blockIdx.x * blockDim.x + threadIdx.x;:这行代码计算了当前线程在 x 轴上的全局坐标。它使用了以下参数:

  • blockIdx.x:表示线程所在的线程块在 x 轴上的索引。
  • blockDim.x:表示线程块的大小,即在 x 轴上的线程数量。
  • threadIdx.x:表示当前线程在线程块内的索引。

通过将线程块的 x 轴索引乘以线程块大小,然后加上线程在线程块内的索引,就可以得到当前线程在 x 轴上的全局坐标。

const int y = blockIdx.y * blockDim.y + threadIdx.y;:这行代码类似地计算了当前线程在 y 轴上的全局坐标,使用了以下参数:

  • blockIdx.y:表示线程所在的线程块在 y 轴上的索引。
  • blockDim.y:表示线程块的大小,即在 y 轴上的线程数量。
  • threadIdx.y:表示当前线程在线程块内的索引。

通过将线程块的 y 轴索引乘以线程块大小,然后加上线程在线程块内的索引,就可以得到当前线程在 y 轴上的全局坐标。

这些计算是为了将线程的相对位置转换为全局坐标,以便在核函数中可以根据这些全局坐标来访问全局内存中的数据。通常,在并行计算中,这些坐标用于确定每个线程处理的数据或任务的位置,以确保并行计算的正确性和协调。

const uchar* src_y = (const uchar*)(img + y * img.step); 

这段代码是用于在图像数据中访问像素值的代码,通常在图像处理或计算中使用。

const uchar* src_y:这是一个指针,指向图像数据中的某一行(y 轴)。const uchar* 表示这是一个指向无符号字符(uchar)的常量指针,即指向图像数据的指针,但不允许修改图像数据。

(const uchar*)(img + y * img.step):这部分代码计算了指向图像数据中特定行的指针,并将其赋给 src_y

  • img 是一个表示图像数据的指针或数据结构。
  • y 是之前计算得到的线程在 y 轴上的全局坐标,它确定了要访问的图像的行。
  • img.step 表示图像中每行的字节数(stride),即图像数据中从一行的末尾到下一行的开头所需的字节数。

y * img.step 计算了当前行在图像数据中的偏移量,然后将其添加到 img 指针上,以获得指向指定行的指针。这意味着 src_y 现在指向了图像数据中的特定行。

通过这种方式,你可以使用 src_y 指针来访问特定行的像素数据,然后使用它进行图像处理或计算。这种方式通常用于CUDA核函数或其他并行计算中,以便高效地处理图像数据。这段代码假定图像是以连续的内存块存储的,每行的数据跟随上一行的数据,而 img.step 可以用于在不同图像布局和内存布局下进行正确的访问。

uchar* dst_y = (uchar*)(dst + y * dst.step);

对于输出的图像dst,通过以上代码找到他的每行。

uchar* dst_y:这是一个指针,指向目标图像数据中的某一行(y 轴)。

(uchar*)(dst + y * dst.step):这部分代码计算了指向目标图像数据中特定行的指针,并将其赋给 dst_y

  • dst 是一个表示目标图像数据的指针或数据结构。
  • y 是之前计算得到的线程在 y 轴上的全局坐标,它确定了要访问的目标图像的行。
  • dst.step 表示目标图像中每行的字节数(stride),即目标图像数据中从一行的末尾到下一行的开头所需的字节数。

y * dst.step 计算了当前行在目标图像数据中的偏移量,然后将其添加到 dst 指针上,以获得指向指定行的指针。这意味着 dst_y 现在指向了目标图像数据中的特定行。

	if (x < width && y < height) {
		dst_y[3 * x] = src_y[3 * x];
		dst_y[3 * x + 1] = src_y[3 * x + 1];
		dst_y[3 * x + 2] = src_y[3 * x + 2];   //三通道
	}

if (x < width && y < height):这是一个条件语句,用于检查当前线程的全局坐标 (x, y) 是否位于图像的有效范围内。widthheight 分别表示图像的宽度和高度。这个条件确保只有在图像范围内的像素才会被处理。

  1. dst_y[3 * x] = src_y[3 * x];:这行代码将源图像 src_y 中的当前像素的红色通道值(第一个通道)复制到目标图像 dst_y 中的相同位置。这是一个三通道的图像,所以每个像素有红色、绿色和蓝色三个通道的值。

  2. dst_y[3 * x + 1] = src_y[3 * x + 1];:这行代码将源图像 src_y 中的当前像素的绿色通道值(第二个通道)复制到目标图像 dst_y 中的相同位置。

  3. dst_y[3 * x + 2] = src_y[3 * x + 2];:这行代码将源图像 src_y 中的当前像素的蓝色通道值(第三个通道)复制到目标图像 dst_y 中的相同位置。

这段代码的目的是将源图像中的像素值(红色、绿色和蓝色通道)复制到目标图像中,实现像素级的图像复制。只有当线程坐标 (x, y) 位于图像的有效范围内时,才会执行此操作,以确保不处理图像范围外的像素。这是一个简单的图像处理操作,通常在并行计算中用于处理图像数据。

可以看到这里源图像数据传进GPU后貌似是rgb3个一组。然后连续的存在GPU中。

主程序:

int main() {

	Mat a = imread("C:/Users/1/Pictures/Saved Pictures/1.jpg");
    //cpu读图
	GpuMat d_a(a);
	GpuMat d_dst(d_a.size(), CV_8UC3);
    //gpu创建原图与目标

	int width = a.size().width;
	int height = a.size().height;
    //图的宽和高
	const int nthreads = 256;
    //每个线程块的线程数256
	dim3 bdim(nthreads, 1);
	dim3 gdim(divUp(width, bdim.x), divUp(height, bdim.y));
    //定义网格大小与线程块大小。
    //这里线程块大小是256X1,网格块是线程块长宽的倍数。

	compute_kernel<nthreads> << <gdim, bdim >> > (height, width, d_a, d_dst);
    //执行核函数

	Mat dst(d_dst);
    //从gpu读出目标图
	imshow("原始图像",a);
	imshow("处理后图像",dst);
    //显示图像
	waitKey();

	return 0;
}

opencv中的Mat类型:

OpenCV中的 Mat 类型是用于表示图像和矩阵数据的核心数据结构。它是OpenCV库中最常用的类之一,用于处理和操作图像、视频和矩阵数据。以下是一些关于OpenCV中Mat类型的重要信息:

  1. 图像和矩阵数据的通用表示: Mat 类允许你表示各种数据,包括单通道和多通道图像、矩阵、向量以及其他形式的数据。

  2. 多通道支持: Mat 支持多通道图像,典型的图像通道数为1(单通道灰度图像)或3(三通道彩色图像)。每个通道都可以包含像素值。

  3. 数据存储: Mat 对象在内部存储其数据,通常以行优先方式存储像素数据。这使得在图像处理和计算中能够高效地访问和操作数据。

  4. 图像大小: 你可以使用 size() 方法获取图像的尺寸,包括宽度和高度。

  5. 像素访问: Mat 对象允许你通过像素坐标访问和修改图像数据,例如 Mat.at<uchar>(y, x) 可以用于访问灰度图像中像素的值。

  6. 数据类型: Mat 可以容纳不同数据类型的像素值,如无符号字符(uchar)、浮点数(float)、双精度浮点数(double)等,这取决于所需的精度和应用。

  7. 复制和引用: Mat 对象可以通过复制数据或引用现有数据来创建,这使得在不复制数据的情况下有效地操作大型图像和矩阵。

  8. 图像加载和保存: OpenCV提供了加载和保存图像数据的函数,可以将图像从文件加载到Mat对象中,并将Mat对象保存为图像文件。

Mat 类型是OpenCV中最常用的数据类型之一,用于图像处理、计算机视觉和机器学习等领域。它提供了广泛的功能和方法,使得处理各种图像和矩阵数据变得更加方便和灵活。

opencv:imread()函数:

imread 是OpenCV库中用于加载图像文件的函数。它允许你从磁盘上的图像文件中读取图像数据并将其加载到OpenCV的 Mat 对象中,以便后续的图像处理和分析。以下是 imread 函数的基本用法和参数:

cv::Mat cv::imread(const std::string& filename, int flags = IMREAD_COLOR);
  • filename:要加载的图像文件的文件名(包括文件路径)。
  • flags:可选参数,指定如何加载图像。它可以取以下常见值:
    • cv::IMREAD_COLOR(默认值):加载图像为彩色图像(三通道BGR)。
    • cv::IMREAD_GRAYSCALE:加载图像为灰度图像(单通道灰度)。
    • cv::IMREAD_UNCHANGED:加载图像的所有通道,包括 alpha 通道(如果有的话)。

imread 函数返回一个 cv::Mat 对象,其中包含从图像文件加载的图像数据。

GpuMat 数据类型:

GpuMat 是OpenCV库中用于在GPU上存储图像和矩阵数据的数据类型。它是OpenCV GPU模块的一部分,允许你在GPU上执行图像处理操作,而不需要将数据从主机(CPU)到设备(GPU)进行复制。

  1. 在GPU上存储数据: GpuMat 允许你将图像和矩阵数据存储在GPU的全局内存中,这使得在GPU上执行操作变得更加高效。

  2. Mat类的相似性: GpuMat 类的使用方式与OpenCV中的Mat类非常相似,它提供了类似的方法和操作。

  3. GPU并行计算: 通过将数据存储在GpuMat对象中,你可以在GPU上使用并行计算来处理图像数据,从而加速处理过程。

  4. 数据传输: 你可以使用uploaddownload等方法在主机和设备之间传输数据。这使得你可以轻松地将数据从CPU上传到GPU进行处理,或者将处理结果下载到CPU进行后续分析。’‘

GpuMat 数据类型的初始化:

要初始化一个 GpuMat 数据类型,你可以使用不同的方式,具体取决于你希望将什么样的数据加载到 GpuMat 对象中。以下是一些常见的初始化方法:

1.从主机(CPU)上的 Mat 对象初始化

cv::Mat cpuMat = cv::imread("image.jpg", cv::IMREAD_COLOR);
cv::GpuMat gpuMat(cpuMat);

这种方式允许你从主机上的 Mat 对象(包含图像数据的CPU内存)初始化一个 GpuMat 对象,从而将数据传输到GPU上。(如我们代码里的 GpuMat d_a(a);)

2.使用 upload 方法从主机上的 Mat 对象上传数据:

cv::Mat cpuMat = cv::imread("image.jpg", cv::IMREAD_COLOR);
cv::GpuMat gpuMat;
gpuMat.upload(cpuMat);

这种方式与第一种方式类似,但是它显式地使用 upload 方法来将数据从 Mat 对象上传到 GpuMat 对象中。

3.使用 create 方法创建一个空的 GpuMat 对象:

cv::GpuMat gpuMat;
gpuMat.create(height, width, CV_8UC3);  // 指定图像的高度、宽度和数据类型

这种方式允许你创建一个空的 GpuMat 对象,然后可以使用其他方法来填充它。

4.从设备(GPU)内存中初始化:

cv::Size size(width, height);
cv::gpu::GpuMat gpuMat(size, CV_8UC3);

这种方式直接在GPU内存上创建 GpuMat 对象,指定图像的大小和数据类型,(如 GpuMat d_dst(d_a.size(), CV_8UC3);)

5.使用构造函数初始化:

cv::gpu::GpuMat gpuMat(height, width, CV_8UC3);

这种方式使用构造函数在GPU上创建 GpuMat 对象,指定图像的大小和数据类型。

数据类型 CV_8UC3:

在OpenCV中,CV_8UC3 是一个图像数据类型的表示,通常用于描述彩色图像。这个数据类型代表了一个三通道的彩色图像,其中每个通道都是一个8位无符号字符(8UC)类型,这意味着每个像素的每个通道都存储为一个8位整数值。具体来说:

  • V_8U 表示像素值的数据类型是8位无符号整数,即像素值的范围是从0到255。
  • C3 表示图像有三个通道,通常代表红色、绿色和蓝色(RGB)通道。

最常见的。

const int nthreads = 256;:这行代码定义了一个常量 nthreads,表示每个线程块中的线程数量。

dim3 bdim(nthreads, 1);dim3 gdim(divUp(width, bdim.x), divUp(height, bdim.y));:这两行代码分别定义了线程块(block)和线程网格(grid)的维度。bdim 表示每个线程块的维度,其中 x 维度的大小是 nthreads,y 维度的大小为1。gdim 表示线程网格的维度,使用 divUp 函数来计算,以确保足够的线程块来覆盖整个图像。

divUp 函数通常用于将一个整数除以另一个整数,并将结果向上取整到最接近的整数。

opencv imshow函数:

imshow 是OpenCV库中用于在图像窗口中显示图像的函数。它是一个用于图像可视化和调试的常用工具。以下是 imshow 函数的基本用法和参数:

void cv::imshow(const cv::String &winname, InputArray mat);

参数解释:

  • winname:要创建的图像窗口的名称。你可以为窗口指定一个字符串,它将成为窗口的标题。
  • mat:要显示的图像数据。通常是一个 Mat 对象,其中包含图像数据。

以上就是教学程序的大概内容,慢慢消化。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值