首先来看看上一节的代码,因为是并没有完整的学过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中的一个封装类,允许你以指针方式访问图像数据,同时提供了图像的宽度和步幅信息。这对于执行图像处理和计算非常有用。
-
提供对图像数据的指针访问:你可以使用
PtrStepb
对象的指针来访问图像数据,这使得你可以执行各种图像处理操作。 -
存储图像的宽度和步幅信息:
PtrStepb
会存储有关图像的宽度和步幅(stride)的信息。步幅是指图像数据中每行之间的字节数差异,这对于访问多通道或不连续存储图像数据非常有用。 -
通常与OpenCV的低级图像处理函数一起使用:
PtrStepb
类通常与OpenCV中的低级图像处理函数一起使用,以便更灵活地访问和处理图像数据。
const PtrStepb img
和 PtrStepb 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)
是否位于图像的有效范围内。width
和 height
分别表示图像的宽度和高度。这个条件确保只有在图像范围内的像素才会被处理。
-
dst_y[3 * x] = src_y[3 * x];
:这行代码将源图像src_y
中的当前像素的红色通道值(第一个通道)复制到目标图像dst_y
中的相同位置。这是一个三通道的图像,所以每个像素有红色、绿色和蓝色三个通道的值。 -
dst_y[3 * x + 1] = src_y[3 * x + 1];
:这行代码将源图像src_y
中的当前像素的绿色通道值(第二个通道)复制到目标图像dst_y
中的相同位置。 -
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
类型的重要信息:
-
图像和矩阵数据的通用表示:
Mat
类允许你表示各种数据,包括单通道和多通道图像、矩阵、向量以及其他形式的数据。 -
多通道支持:
Mat
支持多通道图像,典型的图像通道数为1(单通道灰度图像)或3(三通道彩色图像)。每个通道都可以包含像素值。 -
数据存储:
Mat
对象在内部存储其数据,通常以行优先方式存储像素数据。这使得在图像处理和计算中能够高效地访问和操作数据。 -
图像大小: 你可以使用
size()
方法获取图像的尺寸,包括宽度和高度。 -
像素访问:
Mat
对象允许你通过像素坐标访问和修改图像数据,例如Mat.at<uchar>(y, x)
可以用于访问灰度图像中像素的值。 -
数据类型:
Mat
可以容纳不同数据类型的像素值,如无符号字符(uchar
)、浮点数(float
)、双精度浮点数(double
)等,这取决于所需的精度和应用。 -
复制和引用:
Mat
对象可以通过复制数据或引用现有数据来创建,这使得在不复制数据的情况下有效地操作大型图像和矩阵。 -
图像加载和保存: 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)进行复制。
-
在GPU上存储数据:
GpuMat
允许你将图像和矩阵数据存储在GPU的全局内存中,这使得在GPU上执行操作变得更加高效。 -
与
Mat
类的相似性:GpuMat
类的使用方式与OpenCV中的Mat
类非常相似,它提供了类似的方法和操作。 -
GPU并行计算: 通过将数据存储在
GpuMat
对象中,你可以在GPU上使用并行计算来处理图像数据,从而加速处理过程。 -
数据传输: 你可以使用
upload
和download
等方法在主机和设备之间传输数据。这使得你可以轻松地将数据从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
对象,其中包含图像数据。
以上就是教学程序的大概内容,慢慢消化。