opencv_cuda版本+cuda学习 (三)图像加法、减法

1.图像加法

基础算法,算是图像融合的一种,将两张图叠加起来。图像加法数学公式:

                              Z(x,y) =0.5\times f(x,y)+0.5\times g(x,y)

看看代码:

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

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

template<int nthreads>
__global__ void compute_kernel(int height, int width, const PtrStepb img1, const PtrStepb img2, PtrStepb dst) {
	const int x = blockIdx.x * blockDim.x + threadIdx.x;
	const int y = blockIdx.y * blockDim.y + threadIdx.y;

	const uchar* src_y1 = (const uchar*)(img1 + y * img1.step);
	const uchar* src_y2 = (const uchar*)(img2 + y * img2.step);
	uchar* dst_y = (uchar*)(dst + y * dst.step);

	if (x < width && y < height) {
		dst_y[3 * x] = src_y1[3 * x] * 0.3 + src_y2[3 * x] * 0.7;
		dst_y[3 * x + 1] = src_y1[3 * x + 1] * 0.3 + src_y2[3 * x + 1] * 0.7;
		dst_y[3 * x + 2] = src_y1[3 * x + 2] * 0.3 + src_y2[3 * x + 2] * 0.7;
	
	}

}

void showImg(Mat Img, string windowname, int height, int width);
int main() {
	
	//读图
	Mat a = imread("C:/Users/1/Pictures/Camera Roll/背景.jpg");
	Mat b = imread("C:/Users/1/Pictures/Camera Roll/人像.jpg");
	//获取人像图像大小
	int height = b.rows;
	int width = b.cols;
	//将背景图resize成人像图像大小,这里是因为两张图大小不同
	resize(a, a, Size(width,height));
	cout << a.size() << endl;
	//showImg(a, "背景",height,width);

	//建立Gpu数据
	GpuMat d_a(a);
	GpuMat d_b(b);
	GpuMat d_dst(d_b.size(), CV_8UC3);


	//核函数执行参数配置
	const int nthreads = 256;

	dim3 bdim(nthreads, 1);
	dim3 gdim(divUp(width, bdim.x), divUp(height, bdim.y));

	compute_kernel<nthreads> << <gdim, bdim >> >(height, width, d_a, d_b, d_dst);

	//下载Gpu结果
	Mat dst(d_dst);

	imwrite("fusion.jpg", dst);
	showImg(a, "背景", height, width);
	showImg(b, "人像", height, width);
	showImg(dst, "图像相加",height,width);
	
	waitKey();

	return 0;
}

void showImg(Mat Img, string windowname,int height,int width) {

	namedWindow(windowname, WINDOW_NORMAL);
	resizeWindow(windowname,  width,height);
	imshow(windowname, Img);
}

结果:

2.图像减法

图像减法与图像加法是反操作,但在相减时可能出现像素点值小于0的情况,所以这里采用的是取绝对值。又因为相减后可能图像大部分都是纯黑色,即像素点值为0;所以在此基础上又加了一个反向算法,方便观察。
代码:
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include<cuda.h>
#include<cuda_device_runtime_api.h>
#include <opencv2/core/cuda.hpp>
#include<opencv2/opencv.hpp>
#include<opencv.hpp>
#include <stdio.h>
#include<iostream>

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

template<int nthreads>
__global__ void compute_kernel2(int height, int width, const PtrStepb img1, const PtrStepb img2, PtrStepb dst) {
	const int x = blockIdx.x * blockDim.x + threadIdx.x;
	const int y = blockIdx.y * blockDim.y + threadIdx.y;

	const uchar* src_y1 = (const uchar*)(img1 + y * img1.step);
	const uchar* src_y2 = (const uchar*)(img2 + y * img2.step);
	uchar* dst_y = (uchar*)(dst + y * dst.step);

	if (x < width && y < height) {
		dst_y[3 * x] = 255 - abs(src_y1[3 * x] - src_y2[3 * x]);
		dst_y[3 * x + 1] = 255 - abs(src_y1[3 * x + 1] - src_y2[3 * x + 1]);
		dst_y[3 * x + 2] = 255 - abs(src_y1[3 * x + 2] - src_y2[3 * x + 2]);

	}

}

void showImg(Mat Img, string windowname, int height, int width);
int main() {

	//读图
	Mat a = imread("C:/Users/1/Pictures/Camera Roll/背景.jpg");
	Mat b = imread("C:/Users/1/Pictures/Camera Roll/人像.jpg");
	//获取人像图像大小
	int height = b.rows;
	int width = b.cols;
	//将背景图resize成人像图像大小,这里是因为两张图大小不同
	resize(a, a, Size(width, height));
	cout << a.size() << endl;
	//showImg(a, "背景",height,width);

	//建立Gpu数据
	GpuMat d_a(a);
	GpuMat d_b(b);
	GpuMat d_dst(d_b.size(), CV_8UC3);


	//核函数执行参数配置
	const int nthreads = 256;

	dim3 bdim(nthreads, 1);
	dim3 gdim(divUp(width, bdim.x), divUp(height, bdim.y));

	compute_kernel2<nthreads> <<<gdim, bdim >>> (height, width, d_a, d_b, d_dst);

	//下载Gpu结果
	Mat dst(d_dst);

	imwrite("fusion.jpg", dst);
	showImg(a, "背景", height, width);
	showImg(b, "人像", height, width);
	showImg(dst, "图像相减", height, width);

	waitKey();

	return 0;
}

void showImg(Mat Img, string windowname, int height, int width) {

	namedWindow(windowname, WINDOW_NORMAL);
	resizeWindow(windowname, width, height);
	imshow(windowname, Img);
}

结果:

如果将反向算法去掉:
以上为显示结果。
这次呢,大概讲讲流程,然后将一些之前没提到的关键问题再探讨探讨。
首先程序的开头是核函数:
template<int nthreads>
__global__ void compute_kernel2(int height, int width, const PtrStepb img1, const PtrStepb img2, PtrStepb dst) {
	const int x = blockIdx.x * blockDim.x + threadIdx.x;
	const int y = blockIdx.y * blockDim.y + threadIdx.y;

	const uchar* src_y1 = (const uchar*)(img1 + y * img1.step);
	const uchar* src_y2 = (const uchar*)(img2 + y * img2.step);
	uchar* dst_y = (uchar*)(dst + y * dst.step);

	if (x < width && y < height) {
		dst_y[3 * x] = abs(src_y1[3 * x] - src_y2[3 * x]);
		dst_y[3 * x + 1] = abs(src_y1[3 * x + 1] - src_y2[3 * x + 1]);
		dst_y[3 * x + 2] = abs(src_y1[3 * x + 2] - src_y2[3 * x + 2]);

	}

}

首先映入眼帘的是CUDA核函数,这里我们看到是将核函数定义为模板函数,模板参数是nthreads,用于指定线程块的大小。

其他核函数内的参数我就不细说了,height,width是为了防止线程多申请的线程参与运算。这里因为我们的图像大小可能不是一个完整的线程块的倍数,所以在申请网格Grid_size时采用的时divup函数,即向上取整。如果图像的长宽除以bdim的两个维度有多余的数字,那么网格块的大小向上取整。这个后面详细讲divup函数。

那么这里由于线程块可能会大于图像大小,使用if语句来确保线程只处理图像内的像素。

在opencv中Mat类包含一个重要的元素,stride,即步幅,步幅指定了在内存中每行数据之间的字节数,以及在每个通道(例如,RGB颜色通道)之间的字节数。步幅信息对于访问图像数据和进行像素操作非常有用,尤其在处理多通道、多维图像或对图像进行裁剪、缩放和滤波时。

先来看一个使用这个stride信息进行图像处理的小例子

1.获取图像某个位置处的像素值

如果你有一个Mat对象 image,并且想要获取坐标为 (x, y) 处的像素值,你可以使用以下代码:

uchar* pixel = image.ptr<uchar>(y);
int b = pixel[x * image.channels()]; // 获取蓝色通道值
int g = pixel[x * image.channels() + 1]; // 获取绿色通道值
int r = pixel[x * image.channels() + 2]; // 获取红色通道值

在这里,image.ptr<uchar>(y) 返回指向行 y 的数据的指针,然后根据通道数(image.channels())和像素的 x 坐标来获取通道值。

2.处理ROI

步幅信息对于处理感兴趣区域非常有用,因为它们允许你访问部分图像数据而不必复制整个图像。

int x = 100; // ROI 左上角 x 坐标
int y = 50;  // ROI 左上角 y 坐标
int roiWidth = 200;  // ROI 的宽度
int roiHeight = 150; // ROI 的高度

// 创建一个 ROI(感兴趣区域)
cv::Mat roi(image, cv::Rect(x, y, roiWidth, roiHeight));

// 使用 ROI 的步幅信息进行像素操作
for (int j = 0; j < roiHeight; j++) {
    uchar* pixel = roi.ptr<uchar>(j);
    for (int i = 0; i < roiWidth; i++) {
        // 处理像素值
    }
}

3.多通道图像操作

如果你处理多通道图像,步幅信息非常有用。你可以使用通道数来导航不同通道的像素值。例如,对于一个3通道的图像:

int b = pixel[x * image.channels()]; // 获取蓝色通道值
int g = pixel[x * image.channels() + 1]; // 获取绿色通道值
int r = pixel[x * image.channels() + 2]; // 获取红色通道值

有了以上铺垫再来看看我们写的:

	const uchar* src_y1 = (const uchar*)(img1 + y * img1.step);
	const uchar* src_y2 = (const uchar*)(img2 + y * img2.step);
	uchar* dst_y = (uchar*)(dst + y * dst.step);

其中src_y1,src_y2,dsr_y,就相当于上面的step;img1是本质是一个指针,知道图像的起点,但是我们是对图像每个像素点操作,那么与线程索引相同,我们也需要一个图像的指针索引。

y表示图像的某一行,这里加上了图像每行的数据长度step,那么就可以得到图像中任一行索引。

先看看x,y索引:这是线程的索引

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

首先我们的图片是放在内存里的,并不是直接放在线程里,线程是我们的运算工具,为了进行运算需要从内存读数据,计算,然后放进内存:

		dst_y[3 * x] = abs(src_y1[3 * x] - src_y2[3 * x]);
		dst_y[3 * x + 1] = abs(src_y1[3 * x + 1] - src_y2[3 * x + 1]);
		dst_y[3 * x + 2] = abs(src_y1[3 * x + 2] - src_y2[3 * x + 2]);

那么在内存中我们的图像数据是什么样的?

对于一个5*5的图像就是这么存在,其中红绿蓝为一组。线程与像素对应,线程的索引x,与图像的索引也相同。但是一个像素点包含3个通道,r,g,b。
第0号线程负责读0,1,2,第1号线程读3,4,5,依次类推。
首先我们这里线程块的索引用两个索引,x,y来标记的。而像素点是由src_y表示图像的哪一行,具体索引r g b是通过3*x,3*x+1,3*x+2,来实现对一个像素点的3个通道读取。
这样就将线程与图像像素一一对应的。
最后一个点,divUp函数用来计算数据划分的块数量。
#define divUp(a, b) ((a + b - 1) / b)
 
在这个宏中, a 是总的数据量, b 是每个块中处理的数据数量。 divUp 宏的目的是将 a 划分成若干块,每块包含 b 个数据元素,同时确保不会漏掉任何数据。这在确定要启动的线程块数量时非常有用。用该函数在确定线程块大小后可以计算网格大小。这个函数在cuda编程中经常用到。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值