计算机视觉-图像缩放算法-cuda实现

一.CUDA

CUDA是显卡厂商NVIDIA(英伟达)推出的运算平台,能够将数据数据复制到GPU,在GPU中进行计算,然后再返回给CPU端。CUDA将GPU称为设备侧或者Device,将CPU称为Host侧,按照下图进行划分,真正进行计算的是其中的Thread。
一张经典的图
CUDA的极简使用流程
1.写核函数,是指在Device上运行的函数,用__global__ 标识。
2.在Device上申请空间,使用CUDA自带的cudaMalloc函数。
3.把要进行计算的数据拷贝到Device,也就是上一步申请到的空间。使用cudaMemcpy函数。
4.在Host中调用核函数,使用Device进行计算。
5.将计算的结果从Device拷贝回Host。

二.图像缩放算法

图像缩放这个事,所有操作图片的人都做过,但是程序是如何计算的?
首先,确定缩放目标图的大小,创建一张空白的目标图。
然后,从原图中为目标图的每一个像素点找到对应的值。
截图成熟的缩放算法有很多,在这里只介绍其中最简单的两个。
最近邻点插值法双线性插值法

三.最近邻点插值法

1.算法

1.针对目标图中每一个像素点,按照缩放比例去原图中找对应的点,比值不为整数的就选目标图中最近的点。
2.给目标图取对应的值。

源图像与目标图像宽的比例为:

srcWidth / dstWidth

源图像与目标图像高的比例为:

srcHeight / dstHeight

由目标图坐标找原图像坐标就是:

srcX = dstX * (srcWidth / dstWidth)
srcY = dstY * (srcHeight / dstHeight)

截图

2.代码

代码使用的是C++、CUDA

//文件名是test1.cu
#include<opencv2/opencv.hpp>
#include<iostream>
#include <string>
#include <stdio.h>
#include<vector>
using namespace cv;
using namespace std;

__global__ void resize1GPU(const unsigned char* src, int srcWidth, int srcHeight, unsigned char* dst, int dstWidth, int dstHeight)
{
	//核函数会在每个thread上运行,这里求的x、y是当前thread的坐标,同时也代表当前要处理的像素的坐标
	int y = blockIdx.y * blockDim.y + threadIdx.y;
	int x = blockIdx.x * blockDim.x + threadIdx.x;	
	if (x >= dstWidth || y >= dstHeight) return;
	//以指针的形式操作图像,outPosition是指目标图像素在内存中的位置 
	int outPosition = y * dstWidth + x;		
	//求取对应原图的像素点,srcPosition是指原图像素在内存中的位置 
	int srcX = x * srcWidth / dstWidth;		//如果出现浮点数,这里就会向下取整,以此来表示最近邻
	int srcY = y * srcHeight / dstHeight;	//(如果不喜欢向下取整,也可以选择四舍五入)
	int srcPosition = srcY * srcWidth + srcX;		
	//为目标图像素赋值。RGB三通道,在内存中的位置是挨着的。
	dst[outPosition * 3 + 0] = src[srcPosition * 3 + 0];		
	dst[outPosition * 3 + 1] = src[srcPosition * 3 + 1];
	dst[outPosition * 3 + 2] = src[srcPosition * 3 + 2];
}

int main() {
	Mat src = imread("1.jpg");		//使用opencv
	int srcWidth = src.cols;
	int srcHeight = src.rows;
	int dstWidth = 256;					//目标图的大小
	int dstHeight = 256;

	unsigned char* devSrc;
	unsigned char* devDst;
	//在GPU上为两张图申请存储空间
	cudaMalloc((void**)&devSrc, srcWidth * srcHeight * 3 * sizeof(unsigned char));				
	cudaMalloc((void**)&devDst, dstWidth * dstHeight * 3 * sizeof(unsigned char));
	//把原图复制到GPU上,注意图片数据格式的变化
	cudaMemcpy(devSrc, (unsigned char*)(src.data), srcWidth * srcHeight * 3 * sizeof(unsigned char), cudaMemcpyHostToDevice);

	dim3 blocks((dstWidth + 15) / 16, (dstHeight + 15) / 16);
	dim3 threads(16, 16);
	//调用核函数,重点关注blocks与threads的设置,这样设置是为了让thread的坐标代表目标图像素的坐标
	resize1GPU <<<blocks, threads>>> (devSrc, srcWidth, srcHeight, devDst, dstWidth, dstHeight);
	//将处理完的目标图拷贝回来
	Mat dst(Size(dstWidth, dstHeight), CV_8UC3);
	cudaMemcpy(dst.data, devDst, dstWidth * dstHeight * 3 * sizeof(unsigned char), cudaMemcpyDeviceToHost);
	//使用opencv保存新图片
    vector<int> comprocession_params;
    comprocession_params.push_back(IMWRITE_PNG_COMPRESSION);
    comprocession_params.push_back(9);
    imwrite("nearest_neighbor.png", dst,comprocession_params);
	cudaFree(devSrc);
	cudaFree(devDst);
	return 0;
}

四.双线性插值法

1.算法

双线性插值的像素对应公式也是根据缩放比例
不一样的是,不再是找原图的1个点,而是找到原图的4个点,然后再求出目标图的像素值。
先思考这样一个问题:如下图,已知下图中所有点的坐标,并且已知Q12,Q22,Q11,Q21四点的值,假设所有点的值符合线性变化,那么如何求P点的值。

图为转载,不知为何,去不掉自己的水印
由线性假设有以下等式:

(R1-Q11) / (Q21-Q11) = (x-x1) / (x2-x1)
(R2-Q12) / (Q22-Q12) = (x-x1) / (x2-x1)
(R2-P) / (R2-R1) = (y2-y) / (y2-y1)

先由前两个等式求出R1,R2,做了一次线性插值
再由R1,R2求出P,又做了一次线性插值,这也是算法名字的由来。

最后求得
转载截图把这个求值算法转移到图像中,将坐标变成离散的情况。Q的四个点的坐标之间相差1,故进一步化简为:

P=(Q11)(x2- x)(y2-y) + (Q21)(x- x1)(y2- y) + (Q12)(x2- x)(- y1) +
(Q22)(x - x1)(y- y1)

1.由目标图中的像素,算出原图中的坐标(浮点数),一定能找到距离这个坐标最近的四个像素点。
2.用双线性插值计算出这点的值。

转载图片

2.代码

//文件名是test2.cu
#include<opencv2/opencv.hpp>
#include<iostream>
#include <string>
#include <stdio.h>
#include<vector>
using namespace cv;
using namespace std;

__global__ void resize2GPU(const unsigned char* src, int srcWidth, int srcHeight, unsigned char* dst, int dstWidth, int dstHeight)
{
	int y = blockIdx.y * blockDim.y + threadIdx.y;
	int x = blockIdx.x * blockDim.x + threadIdx.x;
	if (x >= dstWidth || y >= dstHeight) return;
	int dstOffset = y * dstWidth + x;	//目标图像素在内存中的位置 
	//根据缩放比例,计算在原图的坐标(浮点值)
	int srcXf = x * ((float)srcWidth / dstWidth);
	int srcYf = y * ((float)srcHeight / dstHeight);
	//向下取整,得到四个像素中,左上的像素坐标
	int srcX = (int)srcXf;
	int srcY = (int)srcYf;		
	//u就是上面算法中的x-x1,1-u就是x2-x
	int u = srcXf - srcX;		
	int v = srcYf - srcY;

	//P=(Q11)(x2- x)(y2-y) + (Q21)(x- x1)(y2- y) + (Q12)(x2- x)(- y1) + (Q22)(x - x1)(y- y1)
	dst[dstOffset] = 0;
	dst[dstOffset] += (1 - u) * (1 - v) * src[(srcY * srcWidth + srcX)];	
	dst[dstOffset] += (1 - u) * v * src[((srcY + 1) * srcWidth + srcX)];
	dst[dstOffset] += u * (1 - v) * src[(srcY * srcWidth + srcX + 1)];
	dst[dstOffset] += u * v * src[((srcY + 1) * srcWidth + srcX + 1)];	
	//(srcY+1)*srcWidth+srcX+1)是右下角的像素点在内存中的位置
}
//主函数和上一个算法代码一样,唯一区别就是,为了代码简单,把图片变成了灰度图
int main() {
	Mat src = imread("1.jpg", 0);
	int srcWidth = src.cols;
	int srcHeight = src.rows;
	int dstWidth = 512;
	int dstHeight = 512;

	unsigned char* devSrc;
	unsigned char* devDst;

	cudaMalloc((void**)&devSrc, srcWidth * srcHeight * sizeof(unsigned char));
	cudaMalloc((void**)&devDst, dstWidth * dstHeight * sizeof(unsigned char));
	cudaMemcpy(devSrc, src.data, srcWidth * srcHeight * sizeof(unsigned char), cudaMemcpyHostToDevice);

	dim3 blocks((dstWidth + 15) / 16, (dstHeight + 15) / 16);
	dim3 threads(16, 16);
	resize2GPU << <blocks, threads >> > (devSrc, srcWidth, srcHeight, devDst, dstWidth, dstHeight);

	Mat dst(Size(dstWidth, dstHeight), CV_8UC1);
	cudaMemcpy(dst.data, devDst, dstWidth * dstHeight * sizeof(unsigned char), cudaMemcpyDeviceToHost);

	vector<int> comprocession_params;
    comprocession_params.push_back(IMWRITE_PNG_COMPRESSION);
    comprocession_params.push_back(9);
    imwrite("Bilinear_Interpolation.png", dst,comprocession_params);
	cudaFree(devSrc);
	cudaFree(devDst);
	return 0;
}

3.遗留问题

1.当出现坐标在边界时,会出现(i+1,j)、(i,j+1)、(i+1,j+1)的点在实际的图中不存在?此时可以用(i,j)、(i-1,j)、(i,j-1)、(i-1,j-1)。
2.当按缩放比计算坐标后,与原图像中的点发生重合,怎么选取剩下3个点?无论我们怎么选取,其实其余3点的权重都是0,所以都不影响。
3.目标图像的原点(0, 0)点和原始图像的原点肯定是重合的,但是目标图的中心点不一定与原图的中心点重合。整体图像会发生偏移。把浮点坐标的计算方法改为:
截图转载
4.我的电脑没有英伟达显卡,租了一个云计算平台来运行.cu格式的代码。
使用以下两个命令行
编译:nvcc test1.cu pkg-config opencv --libs --cflags opencv -o test1
运行:./test1

CUDA-图像缩放

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值