PyOpenCL-03.图片操作

本文介绍了如何结合Python的PyOpenCL库,利用OpenCL的并行计算能力将彩色图片转换为灰度图。通过创建OpenCL上下文、命令队列,加载内核代码,将图片数据从主机复制到设备,执行内核函数,然后将结果从设备读回主机,最终保存处理后的灰度图片。此外,还提供了一个C++版本的实现作为对比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python是目前较流行的一种科学计算语言。语法简洁,上手快,易于维护的优点。但其运算速度是真心的慢。那我们能否利用Python的简洁+OpenCL的运算能力呢?答案是可以的,那就是PyOpenCL。

目录

 预备知识

实现代码

main.py源代码

gray.cl内核代码

C++版本

运行结果


 预备知识

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;
}

运行结果

原图修改后
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sun zi chao

你的鼓励是我最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值