OpenCL + OpenCV 图像旋转
▶ 使用 OpenCV 从文件读取彩色的 png 图像,旋转一定角度以后写回文件
● 代码,核函数
// rotate.cl
//__constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_FILTER_NEAREST | CLK_ADDRESS_CLAMP;// 设备采样器,可以启用,并删除函数 imageRotate 中的采样器参数 __kernel void imageRotate(__read_only image2d_t inputImage, __write_only image2d_t outputImage, float angle, sampler_t sampler)
{
const int width = get_image_width(inputImage), height = get_image_height(inputImage);
const int halfWidth = width / , halfHeight = height / ;
const int x = get_global_id(), y = get_global_id();
const int xt = x - halfWidth, yt = y - halfHeight;
const float sinFactor = sin(angle), cosFactor = cos(angle); float2 readCoord = (float2)(halfWidth + cosFactor * xt - sinFactor * yt, readCoord.y = halfHeight + sinFactor * xt + cosFactor * yt);
float4 value = read_imagef(inputImage, sampler, readCoord);
write_imagef(outputImage, (int2)(x, y), value);
return;
}
● 代码,分三通道分别旋转
#include <stdio.h>
#include <stdlib.h>
#include <cl.h>
#include <opencv.hpp>
#include <opencv2\core\cvstd.hpp> // namespace cv 的定义 #pragma warning(disable : 4996) // 解封OPenCL1.2 using namespace cv; const char *sourceProgram = "D:/Code/OpenCL/rotate.cl";// 核函数文件
const char *imagePath = "D:\\input.png";
const float angle = 3.14f / ; int readSource(const char* kernelPath, char **output)// 读取文本文件,存储为 char *
{
FILE *fp;
int size;
fopen_s(&fp, kernelPath, "rb");
if (!fp)
{
printf("Open kernel file failed\n");
exit(-);
}
if (fseek(fp, , SEEK_END) != )
{
printf("Seek end of file faildd\n");
exit(-);
}
if ((size = ftell(fp)) < )
{
printf("Get file position failed\n");
exit(-);
}
rewind(fp);
if ((*output = (char *)malloc(size + )) == NULL)
{
printf("Allocate space failed\n");
exit(-);
}
fread((void*)*output, , size, fp);
fclose(fp);
(*output)[size] = '\0';
printf("readSource succeed, program file: %s\n", kernelPath);
return size;
} int main()
{
// 准备平台,设备,上下文,命令队列部分
cl_int status;
cl_uint nPlatform;
clGetPlatformIDs(, NULL, &nPlatform);
cl_platform_id *listPlatform = (cl_platform_id*)malloc(nPlatform * sizeof(cl_platform_id));
clGetPlatformIDs(nPlatform, listPlatform, NULL);
cl_uint nDevice = ;
clGetDeviceIDs(listPlatform[], CL_DEVICE_TYPE_ALL, , NULL, &nDevice);
cl_device_id *listDevice = (cl_device_id*)malloc(nDevice * sizeof(cl_device_id));
clGetDeviceIDs(listPlatform[], CL_DEVICE_TYPE_ALL, nDevice, listDevice, NULL);
cl_context context = clCreateContext(NULL, nDevice, listDevice, NULL, NULL, &status);
cl_command_queue queue = clCreateCommandQueue(context, listDevice[], , &status); // OpenCL1.2
//cl_command_queue_properties queueProp[5] = {CL_QUEUE_PROPERTIES, CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT,// OpenCL2.0
// CL_QUEUE_SIZE, CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE,
// 0};
//cl_command_queue queue = clCreateCommandQueueWithProperties(context, listDevice[0], &queueProp, &status); // 第三个参数 queueProp 各种改都会报内存越界 0xC0000005 // 图片相关
Mat image, channel[];
image = imread(imagePath);
split(image, channel); // 拆分为三通道,分别旋转后拼合
const int imageHeight = image.rows, imageWidth = image.cols;
unsigned char *imageData = (unsigned char*)malloc(sizeof(unsigned char) * imageHeight * imageWidth); cl_image_format format;
format.image_channel_order = CL_R; // 单通道
format.image_channel_data_type = CL_UNORM_INT8; // 无符号 8 位整形,0 ~ 255
cl_image_desc desc;
desc.image_type = CL_MEM_OBJECT_IMAGE2D; // 可以 memset(desc,sizeof(cl_image_desc)); 后仅对前三项赋值
desc.image_width = imageWidth;
desc.image_height = imageHeight;
desc.image_depth = ;
desc.image_array_size = ;
desc.image_row_pitch = ;
desc.image_slice_pitch = ;
desc.num_mip_levels = ;
desc.num_samples = ;
desc.buffer = NULL;
cl_mem d_inputImage = clCreateImage(context, CL_MEM_READ_ONLY, &format, &desc, NULL, &status);
cl_mem d_outputImage = clCreateImage(context, CL_MEM_WRITE_ONLY, &format, &desc, NULL, &status); // 采样器
cl_sampler sampler = clCreateSampler(context, CL_FALSE, CL_ADDRESS_CLAMP_TO_EDGE, CL_FILTER_NEAREST, &status); // OpenCL1.2
//cl_sampler_properties samplerProp[7] = {CL_SAMPLER_NORMALIZED_COORDS, CL_FALSE, // OpenCL2.0
// CL_SAMPLER_ADDRESSING_MODE, CL_ADDRESS_CLAMP_TO_EDGE,
// CL_SAMPLER_FILTER_MODE, CL_FILTER_NEAREST,
// 0};
//cl_sampler sampler = clCreateSamplerWithProperties(context, samplerProp, &status); // 也是内存越界,用不了 // 程序和内核
char* source = NULL;
const size_t lenSource = readSource(sourceProgram, &source);
cl_program program = clCreateProgramWithSource(context, , (const char **)&source, &lenSource, &status);
clBuildProgram(program, , listDevice, NULL, NULL, NULL);
cl_kernel kernel = clCreateKernel(program, "imageRotate", &status);
clSetKernelArg(kernel, , sizeof(cl_mem), &d_inputImage);
clSetKernelArg(kernel, , sizeof(cl_mem), &d_outputImage);
clSetKernelArg(kernel, , sizeof(cl_float), &angle);
clSetKernelArg(kernel, , sizeof(cl_sampler), &sampler);
size_t origin[] = { , , }, region[] = { imageWidth, imageHeight, };// 拷贝图片缓冲区时使用的起点和范围参数
size_t globalSize[] = { imageWidth, imageHeight }; for (int i = ; i < ; i++)// 分三个通道拷入缓冲区,执行旋转操作,拷回内存
{
memcpy(imageData, channel[i].data, sizeof(unsigned char) * imageHeight * imageWidth);
clEnqueueWriteImage(queue, d_inputImage, CL_TRUE, origin, region, , , imageData, , NULL, NULL);
clEnqueueNDRangeKernel(queue, kernel, , NULL, globalSize, NULL, , NULL, NULL);
clEnqueueReadImage(queue, d_outputImage, CL_TRUE, origin, region, , , imageData, , NULL, NULL);
memcpy(channel[i].data, imageData, sizeof(unsigned char) * imageHeight * imageWidth);
} merge(channel, , image);// 合并通道,结果写入文件,在窗口中展示结果
imwrite("D:/output.png", image);
imshow("Result", image);
waitKey(); free(listPlatform);
free(listDevice);
clReleaseContext(context);
clReleaseMemObject(d_inputImage);
clReleaseMemObject(d_outputImage);
clReleaseCommandQueue(queue);
clReleaseProgram(program);
clReleaseKernel(kernel);
//getchar();
return ;
}
● 代码,四个通道同时操作,注意图片读入和输出的时候只有三个通道,需要进行调整
#include <stdio.h>
#include <stdlib.h>
#include <cl.h>
#include <opencv.hpp>
#include <opencv2\core\cvstd.hpp> // namespace cv 的定义 #pragma warning(disable : 4996) // 解封OPenCL1.2 using namespace cv; const char *sourceProgram = "D:/Code/OpenCL/rotate.cl";// 核函数文件
const char *imagePath = "D:/input.png";
const float angle = 3.14f / ; int readSource(const char* kernelPath, char **output)// 读取文本文件,存储为 char *
{
FILE *fp;
int size;
fopen_s(&fp, kernelPath, "rb");
if (!fp)
{
printf("Open kernel file failed\n");
exit(-);
}
if (fseek(fp, , SEEK_END) != )
{
printf("Seek end of file faildd\n");
exit(-);
}
if ((size = ftell(fp)) < )
{
printf("Get file position failed\n");
exit(-);
}
rewind(fp);
if ((*output = (char *)malloc(size + )) == NULL)
{
printf("Allocate space failed\n");
exit(-);
}
fread((void*)*output, , size, fp);
fclose(fp);
(*output)[size] = '\0';
printf("readSource succeed, program file: %s\n", kernelPath);
return size;
} int main()
{
// 准备平台,设备,上下文,命令队列部分
cl_int status;
cl_uint nPlatform;
clGetPlatformIDs(, NULL, &nPlatform);
cl_platform_id *listPlatform = (cl_platform_id*)malloc(nPlatform * sizeof(cl_platform_id));
clGetPlatformIDs(nPlatform, listPlatform, NULL);
cl_uint nDevice = ;
clGetDeviceIDs(listPlatform[], CL_DEVICE_TYPE_ALL, , NULL, &nDevice);
cl_device_id *listDevice = (cl_device_id*)malloc(nDevice * sizeof(cl_device_id));
clGetDeviceIDs(listPlatform[], CL_DEVICE_TYPE_ALL, nDevice, listDevice, NULL);
cl_context context = clCreateContext(NULL, nDevice, listDevice, NULL, NULL, &status);
cl_command_queue queue = clCreateCommandQueue(context, listDevice[], , &status);// OpenCL1.2 // 图片相关
Mat image = imread(imagePath);
const int imageHeight = image.rows, imageWidth = image.cols;
unsigned char *imageData = (unsigned char*)malloc(sizeof(unsigned char) * imageHeight * imageWidth * ); for (int i = ; i < imageWidth * imageHeight; i++)// imread 读进来只有 RGB 三个通道(可能跟图片本身有关),要补成 4 个通道
{
imageData[ * i + ] = image.data[ * i + ];//R
imageData[ * i + ] = image.data[ * i + ];//G
imageData[ * i + ] = image.data[ * i + ];//B
imageData[ * i + ] = ; //A
} cl_image_format format;
format.image_channel_order = CL_RGBA; // 合并通道
format.image_channel_data_type = CL_UNORM_INT8; // 无符号 8 位整形,0 ~ 255
cl_image_desc desc;
desc.image_type = CL_MEM_OBJECT_IMAGE2D; // 可以 memset(desc,sizeof(cl_image_desc)); 后仅对前三项赋值
desc.image_width = imageWidth;
desc.image_height = imageHeight;
desc.image_depth = ;
desc.image_array_size = ;
desc.image_row_pitch = ;
desc.image_slice_pitch = ;
desc.num_mip_levels = ;
desc.num_samples = ;
desc.buffer = NULL;
cl_mem d_inputImage = clCreateImage(context, CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR, &format, &desc, imageData, &status);// 输入图片直接在主机上
cl_mem d_outputImage = clCreateImage(context, CL_MEM_WRITE_ONLY, &format, &desc, NULL, &status); // 采样器
cl_sampler sampler = clCreateSampler(context, CL_FALSE, CL_ADDRESS_CLAMP_TO_EDGE, CL_FILTER_NEAREST, &status); // OpenCL1.2 // 程序和内核
char* source = NULL;
const size_t lenSource = readSource(sourceProgram, &source);
cl_program program = clCreateProgramWithSource(context, , (const char **)&source, &lenSource, &status);
clBuildProgram(program, , listDevice, NULL, NULL, NULL);
cl_kernel kernel = clCreateKernel(program, "imageRotate", &status);
clSetKernelArg(kernel, , sizeof(cl_mem), &d_inputImage);
clSetKernelArg(kernel, , sizeof(cl_mem), &d_outputImage);
clSetKernelArg(kernel, , sizeof(cl_float), &angle);
clSetKernelArg(kernel, , sizeof(cl_sampler), &sampler);
size_t origin[] = { , , }, region[] = { imageWidth, imageHeight, };// 拷贝图片缓冲区时使用的起点和范围参数
size_t globalSize[] = { imageWidth, imageHeight }; clEnqueueNDRangeKernel(queue, kernel, , NULL, globalSize, NULL, , NULL, NULL);
clEnqueueReadImage(queue, d_outputImage, CL_TRUE, origin, region, , , imageData, , NULL, NULL); for (int i = ; i < imageWidth * imageHeight; i++)// 去掉第 4 个通道,返回 image 中
{
image.data[ * i + ] = imageData[ * i + ];//B
image.data[ * i + ] = imageData[ * i + ];//G
image.data[ * i + ] = imageData[ * i + ];//R
} imwrite("D:/output.png", image);
imshow("Result", image);
waitKey(); free(listPlatform);
free(listDevice);
clReleaseContext(context);
clReleaseMemObject(d_inputImage);
clReleaseMemObject(d_outputImage);
clReleaseCommandQueue(queue);
clReleaseProgram(program);
clReleaseKernel(kernel);
//getchar();
return ;
}
● 输入、输出结果,顺时针转 45 度,因为使用了最近邻采样,结果中锯齿比较严重


● 另一种解封旧 API 的方法,在 包含头文件 <cl.h> 前使用 #define CL_USE_DEPRECATED_OPENCL_1_2_APIS ,其中 1_2 可以改成 1_0,1_1 等(https://stackoverflow.com/questions/28500496/opencl-function-found-deprecated-by-visual-studio/28500846#28500846)
● 使用 cl_command_queue_properties 和函数 clCreateCommandQueueWithProperties 来创建命令队列,或是用 cl_sampler_properties 和函数 clCreateSamplerWithProperties 来创建采样器都失败了,报内存访问越界错误(0xC0000005),无论是按格式书写还是把 queueProp 改成 0,创建时第三个参数写成 &queueProp 都不行;有人说更新显卡驱动以后就好了(https://stackoverflow.com/questions/39864947/opencl-cl-out-of-host-memory-on-clcreatecommandqueuewithproperties-with-minima)。最后解决了,用 AMD APP SDK 下面的动态库 amdocl64.dll 替换掉 C:\Windows\System32 里边那个相同库就好了,可以完全使用 OpenCL2.0 的 API,不再报错。
● cv::imread 读入的图片是按照 [ R, G, B, R, G, B, R, G, B, ...] 存放的,在用 OpenCL处理之前需要进行一定的预处理,要么用 split 分解各通道为单独的图片,要么手工拆解,算完以后也要按照这种存放方式转回图像数据中。在发现通道个数和顺序的问题前,要么在调用函数 clCreateImage 的时候返回 -37,-38,-39,要么直接旋转得到像下面这样的图片。以后记得,如果出现这种交叉条纹的图像,有可能是通道交错导致的。

● 吐槽一下,网上能找到的 OpenCL + OpenCV 做图片旋转的基本上有几个版本(https://blog.csdn.net/c602273091/article/details/45418223,https://blog.csdn.net/icamera0/article/details/71598323,https://blog.csdn.net/jaccen2012/article/details/51367388)都是用 FreeImage 库把图像处理成灰度图来旋转的(参考了 刘文志等(2016). OpenCL 异构并行计算[M]. 的代码?),输出肯定是灰度图了,然后大家博客就相互抄吧,全是垃圾。好不容易找到一个彩色的(https://blog.csdn.net/Bob_Dong/article/details/64906734)代码还看不了。
OpenCL + OpenCV 图像旋转的更多相关文章
- OpenCV:OpenCV图像旋转的代码
OpenCV图像旋转的代码 cv::transpose( bfM, bfM ) 前提:使用两个矩阵Mat型进行下标操作是不行的,耗费的时间太长了.直接使用两个指针对拷贝才是王道.不知道和OpenCV比 ...
- opencv 图像旋转
理论 http://www.cnblogs.com/wangguchangqing/p/4045150.html 翻开任意一本图像处理的书,都会讲到图像的几何变换,这里面包括:仿射变换(affine ...
- OpenCV图像旋转
图像旋转是指图像按照某个位置转动一定角度的过程,旋转中图像仍保持这原始尺寸.图像旋转后图像的水平对称轴.垂直对称轴及中心坐标原点都可能会发生变换,因此需要对图像旋转中的坐标进行相应转换. 如下图: 假 ...
- OpenCV 图像旋转实现
1 旋转矩形 首先建议阅读图像旋转算法原理-旋转矩阵,这篇博客可以让你很好地理解图像中的每一个点是如何进行旋转操作的.其中涉及到了图像原点与笛卡尔坐标原点之间的相互转换以及点旋转的一些公式推导. 这里 ...
- opencv 图像仿射变换 计算仿射变换后对应特征点的新坐标 图像旋转、缩放、平移
常常需要最图像进行仿射变换,仿射变换后,我们可能需要将原来图像中的特征点坐标进行重新计算,获得原来图像中例如眼睛瞳孔坐标的新的位置,用于在新得到图像中继续利用瞳孔位置坐标. 仿射变换在:http:// ...
- [opencv] 图像几何变换:旋转,缩放,斜切
几何变换 几何变换可以看成图像中物体(或像素)空间位置改变,或者说是像素的移动. 几何运算需要空间变换和灰度级差值两个步骤的算法,像素通过变换映射到新的坐标位置,新的位置可能是在几个像素之间,即不一定 ...
- 基于c++和opencv底层的图像旋转
图像旋转:本质上是对旋转后的图片中的每个像素计算在原图的位置. 在opencv包里有自带的旋转函数,当你知道倾斜角度theta时: 用getRotationMatrix2D可得2X3的旋转变换矩阵 M ...
- OpenCV计算机视觉学习(11)——图像空间几何变换(图像缩放,图像旋转,图像翻转,图像平移,仿射变换,镜像变换)
如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 图像 ...
- 【OpenCV学习笔记】之六 手写图像旋转函数---万丈高楼平地起
话说,平凡之处显真格,这一点也没错! 比如,对旋转图像进行双线性插值,很简单吧? 可,对我,折腾了大半天,也没有达到预期效果! 尤其是三个误区让我抓瞎好久: 1,坐标旋转公式. 这东西,要用 ...
随机推荐
- 《DSP using MATLAB》Problem 3.17
用差分方程两边进行z变换,再变量带换得到频率响应函数(或转移函数,即LTI系统脉冲响应的DTFT). 代码: %% ------------------------------------------ ...
- asp.net 导出excel的一种方法
项目用到的一种导出excel 的方法予以记录:(具体的业务类可更具情况替换使用) protected void Export(string filename, List<ComponentCon ...
- MySQL · 特性分析 · 优化器 MRR & BKA【转】
MySQL · 特性分析 · 优化器 MRR & BKA 上一篇文章咱们对 ICP 进行了一次全面的分析,本篇文章小编继续为大家分析优化器的另外两个选项: MRR & batched_ ...
- [NN] 对于BackPropagation(BP, 误差反向传播)的一些理解
本文大量参照 David E. Rumelhart, Geoffrey E. Hinton and Ronald J. Williams, Learning representation by bac ...
- js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。
js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } ClassA ...
- Robomongo,Mongo可视化工具
哇唔,其实她是三(阴险脸). 你看你看,界面清新,让人家心旷神怡(害羞),谁还想win+R+mongo呀呀呀?! 哎呀呀,继续···说正事. 在这里···借助SQL进一步理解下MongoDB SQL术 ...
- 【shell】sed命令
sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以将数据行进行替换.删除.新增.选取等特定工作,下面先了解一下sed的用法sed命令行格式为: sed ...
- CentOS7.1下生产环境Keepalived+Nginx配置
CentOS7.1下生产环境Keepalived+Nginx配置 [日期:2015-07-20] 来源:Linux社区 作者:soulful [字体:大 中 小] 注:下文涉及到配置的,如无特别 ...
- Asp.Net中使用Newtonsoft.Json转换,读取,写入
using Newtonsoft.Json;using Newtonsoft.Json.Converters; //把Json字符串反序列化为对象目标对象 = JsonConvert.Deserial ...
- css position 和 块级/行内元素解释
一.position 属性: static:元素框正常生成.块级元素生成一个矩形框,作为文档流的一部分,行内元素则会创建一个或多个行框,置于其父元素中. relative:元素框偏移某个距离.元素仍保 ...