Python是目前较流行的一种科学计算语言。语法简洁,上手快,易于维护的优点。但其运算速度是真心的慢。那我们能否利用Python的简洁+OpenCL的运算能力呢?答案是可以的,那就是PyOpenCL。
目录
预备知识
OpenCL的内存对象被分为两类,而这两类内存对象的具体位置、布局以及格式由参数来定义。这两类内存对象是buffer和image。
buffer内存对象:以传统CPU的意义看,buffer对象是一维数组,类似C程序中的malloc函数。buffer对象支持的数据类型可以是任意标题、向量以及用户自定义的数据类型。buffer对象中存储 的数据是连续的,这就使OpenCL Kernel可以用C程序员熟悉的指针随机访问方式来读取它们。
image内存对象:image对象采用的是不同的方法。因为GPU是为处理图像任务而设计的,所以对图像数据的访问进行了深入优化。image与buffer主要区别:
- 对设备代码是不可见的,不能通过指针直接访问。
- 多维结构。
- 仅限于图像数据相关数据类型,而不能自由实现任意数据类型。
在处理图像数据时。用image内存较buffer内存更有效率。这节我们学习如何用image内存处理数据。把一张彩色图片转换成灰度图。
实现代码
这里将实现一个将彩色图片转换成灰度图的程序。
main.py源代码
import pyopencl as cl
import numpy as np
from PIL import Image
def RoundUp(groupSize, imgSize):
r = imgSize % groupSize;
if r == 0:
return imgSize
else:
return imgSize + groupSize - r
if __name__ == '__main__':
#step 1:选择并创建上下文
ctx = cl.create_some_context()
#step 2:创建命令队列
queue = cl.CommandQueue(ctx)
#加载并创建CL程序
f_cl = open("gray.cl","r")
f_buf = f_cl.read()
# 通过字符串内容编译OpenCL的Program
prg = cl.Program(ctx, f_buf).build()
src = Image.open('bg.png')
dst = Image.new('RGBA',src.size,(0,0,0))
#获得OpenCL内存标志集
mf = cl.mem_flags
# OpenCL处理的图片文件格式RGBA,unit8
imageFormat = cl.ImageFormat(cl.channel_order.RGBA,cl.channel_type.UNSIGNED_INT8)
# 将图片从Host复制到Device
img_in = cl.Image(ctx,mf.READ_ONLY | mf.COPY_HOST_PTR,imageFormat,src.size,None,src.tobytes())
img_out = cl.Image(context=ctx,flags=mf.WRITE_ONLY,format=imageFormat,shape=src.size)
# 根据图片大小定义WorkSize
localWorkSize = ( 8, 8 )
globalWorkSize = ( RoundUp(localWorkSize[0], src.size[0]),
RoundUp(localWorkSize[1],src.size[1]))
# 执行Kernel
prg.gray_filter(queue,globalWorkSize,localWorkSize,img_in,img_out)
buffer = np.zeros(src.size[0] * src.size[1] * 4, np.uint8)
origin = ( 0, 0, 0 )
region = ( src.size[0], src.size[1], 1 )
# 将处理好的图片从设备复制到HOST
cl._enqueue_read_image(queue, img_out,origin, region, buffer).wait()
# 保存图片
dist = Image.frombytes("RGBA",src.size, buffer.tobytes())
dist.save('gray.png')
dist.show()
gray.cl内核代码
__kernel void gray_filter(__read_only image2d_t in_img,
__write_only image2d_t out_img)
{
const sampler_t sampler = CLK_FILTER_NEAREST |
CLK_NORMALIZED_COORDS_FALSE |
CLK_ADDRESS_CLAMP;
int col = get_global_id(0);
int row = get_global_id(1);
int2 coord = (int2)(col,row);
uint4 pixel = read_imageui(in_img,sampler,coord);
uint v = pixel.x*0.299f + pixel.y*0.587f + pixel.z*0.114f;
write_imageui(out_img,coord,(uint4)(v,v,v,255));
}
C++版本
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <FreeImage.h>//包含头文件
#include <CL/cl.h>//包含CL的头文件
using namespace std;
//加载图片文件
unsigned char* load_image(const char* fname,int& width,int& height)
{
unsigned char* imgBuf;
//初始化FreeImage
FreeImage_Initialise(TRUE);
//定义图片格式为未知
FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
//获取图片格式
fif = FreeImage_GetFileType(fname,0);
//根据获取格式读取图片数据
FIBITMAP* bitmap = FreeImage_Load(fif,fname,0);
if(!bitmap)
{
printf("load error!\n");
return NULL;
}
int x,y;
RGBQUAD m_rgb;
//获取图片长宽
width = (int)FreeImage_GetWidth(bitmap);
height = (int)FreeImage_GetHeight(bitmap);
//每个像素存储RGBA四组信息,没有A的用255填充。
//像素每位范围(0~255)
imgBuf = new unsigned char[width*height*4];
//获取图片数据
//按RGBA格式保存到数组中
for(y=0;y<height;y++)
{
for(x=0;x<width;x++)
{
//获取像素值
FreeImage_GetPixelColor(bitmap,x,y,&m_rgb);
//将RGB值存入数组
imgBuf[y*width*4+x*4+0] = m_rgb.rgbRed;
imgBuf[y*width*4+x*4+1] = m_rgb.rgbGreen;
imgBuf[y*width*4+x*4+2] = m_rgb.rgbBlue;
//判断是否透明图片
//如果是就取alpha值保存
if(FreeImage_IsTransparent(bitmap))
imgBuf[y*width*4+x*4+3] = m_rgb.rgbReserved;
else
imgBuf[y*width*4+x*4+3] = 255;
}
}
//释放FreeImage
FreeImage_Unload(bitmap);
return imgBuf;
}
//保存图片
static bool SaveImg(int width,int height,unsigned char* Img_data)
{
//初始化FreeImage
FreeImage_Initialise(TRUE);
FIBITMAP* bitmap =FreeImage_Allocate(width,height,32,8,8,8);
int m,n;
for(n=0;n<height;n++)
{
BYTE *bits =FreeImage_GetScanLine(bitmap,n);
for(m=0;m<width;m++)
{
bits[2] = Img_data[width*4*n+m*4+0];
bits[1] = Img_data[width*4*n+m*4+1];
bits[0] = Img_data[width*4*n+m*4+2];
bits[3] = Img_data[width*4*n+m*4+3];
bits+=4;
}
}
//保存图片为PNG格式
if(false==FreeImage_Save(FIF_PNG, bitmap,"gray.png",PNG_DEFAULT))
{
cout<<"save image error"<<endl;
return false;
}
FreeImage_Unload(bitmap);
return true;
}
//从外部文件获取cl内核代码
bool GetFileData(const char* fname,string& str)
{
FILE* fp = fopen(fname,"r");
if(fp==NULL)
{
printf("no found file\n");
return false;
}
int n=0;
while(feof(fp)==0)
{
str += fgetc(fp);
}
return true;
}
//主程序
int main()
{
int width=0,height=0;
//读取图片文件数据
unsigned char* Img_data = load_image("test.jpg",width,height);
if(Img_data==NULL)
{
cout<<"load image:test.jpg error!"<<endl;
return 0;
}
size_t globalWorkSize[1];
globalWorkSize[0] = width * height;
size_t datasize = width * height * 4 * sizeof(unsigned char);
//先读外部CL核心代码,如果失败则退出。
//代码存buf_code里面
string code_file;
if(false == GetFileData("gray.cl",code_file))
return 0;
char* buf_code = new char[code_file.size()];
strcpy(buf_code,code_file.c_str());
buf_code[code_file.size()-1] = NULL;
//声明CL所需变量。
cl_device_id device;
cl_platform_id platform_id = NULL;
cl_context context;
cl_command_queue cmdQueue;
cl_mem bufferC;
cl_program program;
cl_kernel kernel = NULL;
cl_int err;
//step 1:初始化OpenCL
err = clGetPlatformIDs(1,&platform_id,NULL);
if(err!=CL_SUCCESS)
{
cout<<"clGetPlatformIDs error"<<endl;
return 0;
}
//这次我们只用CPU来进行并行运算,当然你也可以该成GPU
clGetDeviceIDs(platform_id,CL_DEVICE_TYPE_CPU,1,&device,NULL);
//step 2:创建上下文
context = clCreateContext(NULL,1,&device,NULL,NULL,NULL);
//step 3:创建命令队列
cmdQueue = clCreateCommandQueue(context,device,0,NULL);
//step 4:创建数据缓冲区
bufferC = clCreateBuffer(context,
CL_MEM_READ_WRITE,
datasize,NULL,NULL);
clEnqueueWriteBuffer(cmdQueue,bufferC,CL_FALSE,
0,datasize,Img_data,0,
NULL,NULL);
//step 5:加载编译代码,创建内核调用函数
program = clCreateProgramWithSource(context,1,
(const char**)&buf_code,
NULL,NULL);
clBuildProgram(program,1,&device,NULL,NULL,NULL);
//step 6:创建内核,调用函数。
kernel = clCreateKernel(program,"gray_filter",NULL);
clSetKernelArg(kernel,0,sizeof(cl_mem),&bufferC);
//step 7:执行内核程序
//注意这里第三个参数已经改成1,表示一维数据。
clEnqueueNDRangeKernel(cmdQueue,kernel,
1,NULL,globalWorkSize,
NULL,0,NULL,NULL);
//step 8:取回计算结果
clEnqueueReadBuffer(cmdQueue,bufferC,CL_TRUE,0,
datasize,Img_data,0,NULL,NULL);
SaveImg(width,height,Img_data);
//释放所有调用和内存
clReleaseKernel(kernel);
clReleaseProgram(program);
clReleaseCommandQueue(cmdQueue);
clReleaseMemObject(bufferC);
clReleaseContext(context);
delete buf_code;
delete Img_data;
return 1;
}
运行结果
原图 | 修改后 |
![]() | ![]() |