使用OpenCL+OpenCV实现图像旋转(一)

[题外话]近期申请了一个微信公众号:平凡程式人生。有兴趣的朋友可以关注,那里将会涉及更多更新OpenCL+OpenCV以及图像处理方面的文章。

最近在学习《OPENCL异构计算》,其中有一个实例是使用OpenCL实现图像旋转。这个实例中并没有涉及读取、保存、显示图像等操作,其中也存在一些小bug。在学习OpenCL之初,完整地实现这个实例还是很有意义的事情。

1、图像旋转原理

所谓图像旋转是指图像以某一点为中心旋转一定的角度,形成一幅新的图像的过程。这个点通常就是图像的中心。

由于是按照中心旋转,所以有这样一个属性:旋转前和旋转后的点离中心的位置不变.

根据这个属性,可以得到旋转后的点的坐标与原坐标的对应关系。

原图像的坐标一般是以左上角为原点的,我们先把坐标转换为以图像中心为原点。假设原图像的宽为w,高为h,(x0,y0)为原坐标内的一点,转换坐标后的点为(x1,y1)。可以得到:

X0’ = x0 - w/2;

y1’ = -y0 + h/2;

在新的坐标系下,假设点(x0,y0)距离原点的距离为r,点与原点之间的连线与x轴的夹角为b,旋转的角度为a,旋转后的点为(x1,y1), 如下图所示。

那么有以下结论:

x0=r*cosb;y0=r*sinb

x1 = r*cos(b-a) = r*cosb*cosa+r*sinb*sina=x0*cosa+y0*sina;

y1=r*sin(b-a)=r*sinb*cosa-r*cosb*sina=-x0*sina+y0*cosa;

得到了转换后的坐标,我们只需要把这些坐标再转换为原坐标系即可。

x1’ = x1+w/2= x0*cosa+y0*sina+w/2

y1’=-y1+h/2=-(-x0*sina+y0*cosa)+h/2= x0*sina-y0*cosa+h/2

此处的x0/y0是新的坐标系中的值,转换为原坐标系为:

x1’ = x0*cosa+y0*sina+w/2=(x00-w/2)*consa+(-y00+h/2)*sina+w/2

y1’= x0*sina-y0*cosa+h/2=( x00-w/2)*sina-(-y00+h/2)*cosa+h/2

=(y00-h/2)*cosa+( x00-w/2)*sina+h/2

2、程序设计

对于图像旋转这个实例,为了处理简单,我将在灰度图上去做旋转。 大致的处理流程如下:

1>     调用OpenCV API imread()读取一张彩色JPEG图片,将它存储在MAT变量中。该变量的data成员中存储着将JPEG图片解码后的RGB数据。

2>     调用OpenCV API cvtColor()将存储RGB数据的MAT变量转换为只存储灰度图像数据的MAT对象。也可以使用函数imread()时直接将JPEG图像解码转换为灰度图像。

3>     MAT对象的成员width和height存储着解码后图像的分辨率信息。根据当前分辨率,分配处理图像时所用的输入buffer和输出buffer。它们都按照存储char型数据进行空间申请。

4>     将MAT对象的成员data中数据copy到输入buffer中。同时将输出buffer初始化为全0。到此,我们调用OpenCV的API所要做的事情告一段落了。接下来就要调用OpenCL的API做事情了。

5>     调用OpenCL API clGetPlatformIDs()直接获取第一个可用的平台信息。该函数一般是先用它获取支持OpenCL平台的数目,然后再次调用它获取某个平台的信息。两次调用,通过传递不同参数区分。

6>     调用OpenCL API clGetDeviceIDs()获取第一个平台中第一个可用的设备。同样,这个函数也可以调用两次,分别获取当前平台的设备数目,再获取某个设备信息。

7>     调用OpenCL API clCreateContext()创建上下文。

8>     调用OpenCL API clCreateCommandQueue()创建host与device之间交互的command队列。

9>     调用OpenCL API clCreateBuffer()在设备端分配存储输入图像的buffer。

10> 调用OpenCL API clEnqueueWriteBuffer()将之前存储灰度图像数据的输入buffer内存copy到设备端buffer中。

11> 调用OpenCL API clCreateBuffer()在设备端分配处理完数据的存储buffer。

12> 调用文件读取函数,将kernel文件ImageRotate.cl中的内容读取到string变量中。

13> 调用OpenCL API clCreateProgramWithSource(),使用kernel的源码创建program对象。

14> 调用OpenCL API clBuildProgram()编译program对象。

15> 调用OpenCL API clCreateKernel(),使用编译完的程序对象创建kernel。

16> 调用OpenCL API clSetKernelArg()为kernel程序传递参数,包括输入输出buffer地址,图像分辨率和sin()\cos()值。

17> 调用OpenCL API clEnqueueNDRangeKernel()执行kernel。

18> 调用OpenCL API clEnqueueReadBuffer,将处理完的图像数据已经从设备端传递到了host端的输出buffer中。

19> 将输出buffer中的数据copy到MAT对象的成员data中。

20> 调用OpenCV API imwrite()将旋转后的灰度图像保存到文件中,编码为JPEG保存起来。

21> 释放输入输出buffer空间,释放OpenCL创建的各个对象。

3、kernel程序代码

我们先看一下kernel程序。Kernel程序是每个work item需要执行的,它需要存储在以cl为后缀的文件中,比如:ImageRotate.cl。

Kernel程序定义如下:

__kernel void img_rotate(

__global unsigned char *dest_data,

__global unsigned char *src_data,

int W,

int H,

float sinTheta,

float cosTheta)

有几点需要注意的地方:

1〉  必须带着关键字__kernel;

2〉  返回值必须为void;

3〉  区分清楚所传参数的存储类型,比如带__global表示存储在global memory中;什么都不带的W、H等表示存储在work item的private memory中。

Kernel程序如下:

1.	__kernel void img_rotate(
2.	    __global unsigned char *dest_data,
3.	    __global unsigned char *src_data,
4.	    int W,
5.	    int H,
6.	    float sinTheta,
7.	    float cosTheta){
8.	        //work item gets its index within index space
9.	        const int ix = get_global_id(0);
10.	        const int iy = get_global_id(1);
11.
12.	        //calculate location of data to move int (ix, iy)
13.	        //output decomposition as mentioned
14.	        float xpos = ((float)(ix - W / 2)) * cosTheta + ((float)(-iy + H / 2)) * sinTheta + W / 2;
15.	        float ypos = ((float)(ix - W / 2)) * sinTheta + ((float)(iy - H / 2)) * cosTheta + H / 2;
16.
17.	        //bound checking
18.	        if (((int)xpos >=0) && ((int)xpos < W) &&
19.	            ((int)ypos >= 0) && ((int)ypos < H)) {
20.	                dest_data[(int)ypos * W + (int)xpos] = src_data[iy * W + ix];
21.	        }
22.	}

  (未完待续)

时间: 2024-10-29 10:47:52

使用OpenCL+OpenCV实现图像旋转(一)的相关文章

【OpenCV】图像旋转详解,边缘用黑色填充

项目要用到图像旋转,OpenCV里面居然没有专门封装好的函数,只好自己写了.根据<learnning OpenCV>发现效果不是很理想,旋转后图像大小不变,可是图像却被裁减了. 例子如下: int main( int argc, char** argv ) { IplImage* src=cvLoadImage("C:\\Users\\Liu\\Desktop\\bridge.bmp",1); IplImage* dst = cvCloneImage( src ); int

用OpenCV实现Photoshop算法(一): 图像旋转

最近学习了OpenCV,于是想用它实现Photoshop的主要功能,用于照片处理. 对于一张照片,PS的一般处理步骤包括: 1, 旋转图片,校正位置. 2,剪切,调整大小,重新构图. 3,调整色阶.曲线,使图片曝光正确.对比适中. 4,调整对比度.饱和度 5,印章去掉不想要的东西,液化调整形体线条 6,对于人像图片,美肤.美白 7, 用色彩平衡.可选颜色等调整色调,形成照片调性 8,加一些光效 9,锐化 以后的一系列博文将采用OpenCV逐一实现Photoshop的算法和功能, 并用计算机视觉人

基于c++和opencv底层的图像旋转

图像旋转:本质上是对旋转后的图片中的每个像素计算在原图的位置. 在opencv包里有自带的旋转函数,当你知道倾斜角度theta时: 用getRotationMatrix2D可得2X3的旋转变换矩阵 M,在用warpaffine函数可得倾斜后的图像dst. 很方便啊,为什么还要自己实现底层的图像旋转呢?因为有些地方你用这两个函数就会出现问题,比如说: 当原图的size是MXN,且图像是完全填充的(因为如果有留白可能还不能将问题完全反映出来),现在你需要将它90°变换(为了形象说明),可是用前面两个

OpenCV文本图像的旋转矫正

用户在使用Android手机拍摄过程中难免会出现文本图像存在旋转角度.这里采用霍夫变换.边缘检测等数字图像处理算法检测图像的旋转角度,并根据计算结果对输入图像进行旋转矫正. 首先定义一个结构元素,再通过该结构元素对该图像进行开运算和闭运算(即腐蚀膨胀运算). Imgproc.cvtColor(matOri, matGray, Imgproc.COLOR_RGB2GRAY); Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_R

opencv 图像变换原理详解 图像平移 图像旋转 图像缩放

常见的2D图像变换从原理上讲主要包括基于2×3矩阵的仿射变换和基于3×3矩阵透视变换. 仿射变换 原理 基本的图像变换就是二维坐标的变换:从一种二维坐标(x,y)到另一种二维坐标(u,v)的线性变换: 如果写成矩阵的形式,就是: 作如下定义: 矩阵T(2×3)就称为仿射变换的变换矩阵,R为线性变换矩阵,t为平移矩阵,简单来说,仿射变换就是线性变换+平移.变换后直线依然是直线,平行线依然是平行线,直线间的相对位置关系不变,因此非共线的三个对应点便可确定唯一的一个仿射变换,线性变换4个自由度+平移2

opencv-从图像旋转学习Mat数据访问

先看一个简单的例子 代码: // ConsoleApplication3_6_23.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include<opencv2/opencv.hpp> #include<iostream> #include<vector> using namespace std; using namespace c

C/C++ BMP(24位真彩色)图像处理(4)------图像の旋转

历经一个多月,CSDN貌似终于好像把文章列表阅读量信息归零BUG给修好了,于是乎放篇做期末大作业时写的文章上来测测效果,可别又像上次一样一发文章就又坑爹了啊! 本篇谈的是图像的旋转,不算是什么新鲜的题目了.但是现在由于很多工具如MATLAB.OPENCV等都把算法写好给用户调用,导致大多用户只知其然不知其所以然,所以回顾一下也是好的. 图像的旋转,说到底就是每个像素点绕着某个圆心旋转一定角度.如果是写代码的话,旋转的角度和圆心应该是已知的条件,我们第一个思路是根据已知条件求取出图像经过旋转后的新

opencl+opencv实现sobel算法

这几天在看opencl编程指南.照着书中的样例实现了sobel算法: 1.结合opencv读取图像,保存到缓冲区中. 2.编写和编译内核.并保存显示处理后的结果. 内核: const sampler_t sampler = CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST; kernel void sobel_rgb(read_only image2d_t src,write_only image2d_t dst) { int x = (int)get

YUV420图像旋转90算法的优化

在做android摄像头捕获时,发现从android摄像头出来的原始视是逆时针旋转了90度的,所以须要把它顺时针旋转90.android视频支持的是NV21格式,它是一种YUV420的格式.当然,始果你用的是android sdk的话,当中image就提供这个能力.可是我是在ndk下开发,没有找到对应的功能(假设你知道请告诉我). 我本想用开源的图像处理库(opencv)做旋转,可是opencv仅仅能处理bmp的图像.这种话,须要先把NV21转换成BMP32.然后再做旋转.所以要操作两次,效率肯