import pycuda.autoinit
import pycuda.driver as drv
import numpy
import matplotlib.pyplot as plt
from pycuda.compiler import SourceModule
mod = SourceModule("""
__global__ void atomic_hist(int *d_b, int *d_a, int SIZE)
{
int tid = threadIdx.x + blockDim.x * blockIdx.x;
int item = d_a[tid];
if (tid < SIZE)
{
atomicAdd(&(d_b[item]), 1);
}
}
""")
atomic_hist = mod.get_function("atomic_hist")
import cv2
h_img = cv2.imread("cameraman.tif",0)
h_a=h_img.flatten()
h_a=h_a.astype(numpy.int)
h_result = numpy.zeros(256).astype(numpy.int)
SIZE = h_img.size
NUM_BIN=256
n_threads= int(numpy.ceil((SIZE+NUM_BIN-1) / NUM_BIN))
start = drv.Event()
end=drv.Event()
start.record()
atomic_hist(drv.Out(h_result), drv.In(h_a), numpy.uint32(SIZE),
block=(n_threads,1,1), grid=(NUM_BIN,1))
end.record()
end.synchronize()
secs = start.time_till(end)*1e-3
print("Time for Calculating Histogram on GPU without shared memory")
print("%fs" % (secs))
plt.stem(h_result)
plt.xlim([0,256])
plt.title("Histogram on GPU")
start = cv2.getTickCount()
hist = cv2.calcHist([h_img],[0],None,[256],[0,256])
end = cv2.getTickCount()
time = (end - start)/ cv2.getTickFrequency()
print("Time for Calculating Histogram using OpenCV")
print("%fs" % (secs))
CUDA提供了一个名为atomicAdd操作的API,以避免并行访问内存位置时出现问题。这是一个阻塞( blocking)操作,意味着当多个线程试图访问同一个内存位置时,一次只能有一个线程访问该内存位置,其他线程必须等待该线程完成并将其结果写入内存。
内核函数有3个参数:第一个参数是储存计算后的直方图数据的输出数组,计算8位图像的数组大小为256﹔第二个参数是图像强度的展平(译者注:一维)数组;第三个参数是展平数组的大小。以像素强度值作为索引的直方图内存位置的值随着由线程索引递增的线程计算而递增,线程总数等于展平数组的大小。
atomicAdd函数用于递增内存位置的值,需要2个参数:第一个是要增加值的内存位置;第二个是这个位置要增加的量。atomicAdd 函数会增加直方图计算的时间成本。
使用get_function()方法创建指向内核函数的指针索引,然后调用OpenCV库读取图像。线程块和线程总数是根据图像大小和直方图容器数计算的。以展平图像数组、空白直方图数组和展平数组大小作为参数传递,同时调用内核函数以及要启动的块和线程数。内核函数返回计算出的直方图,可以显示或绘制直方图。
共享内存在GPU设备的芯片上可用,比全局内存快得多,共享内存延迟大约是未缓存的全局内存延迟的1/100。同一线程块中的所有线程都可以访问相同的一段共享内存,这在许多线程需要与其他线程共享结果的应用程序中非常有用。但是如果不同步,也会造成混乱或错误的结果。如果某线程在另一个线程写入内存之前从内存中读取数据,则可能导致错误的结果。因此,应该正确地控制或管理内存访问,可通过_syncthreads()指令来完成。这个指令确保在继续执行程序之前完成对内存的所有写入操作,也被称为barrier。barrier的含义是块中的所有线程都将到达该行,并等待其他线程完成。当所有线程都到达这里后,它们就可以往下一步执行。
import pycuda.autoinit
import pycuda.driver as drv
import numpy
import matplotlib.pyplot as plt
from pycuda.compiler import SourceModule
mod1 = SourceModule("""
__global__ void atomic_hist(int *d_b, int *d_a, int SIZE)
{
int tid = threadIdx.x + blockDim.x * blockIdx.x;
int offset = blockDim.x * gridDim.x;
__shared__ int cache[256];
cache[threadIdx.x] = 0;
__syncthreads();
while (tid < SIZE)
{
atomicAdd(&(cache[d_a[tid]]), 1);
tid += offset;
}
__syncthreads();
atomicAdd(&(d_b[threadIdx.x]), cache[threadIdx.x]);
}
""")
atomic_hist = mod1.get_function("atomic_hist")
import cv2
h_img = cv2.imread("cameraman.tif",0)
h_a=h_img.flatten()
h_a=h_a.astype(numpy.int)
h_result = numpy.zeros(256).astype(numpy.int)
SIZE = h_img.size
NUM_BIN=256
n_threads= int(numpy.ceil((SIZE+NUM_BIN-1) / NUM_BIN))
start = drv.Event()
end=drv.Event()
start.record()
atomic_hist(
drv.Out(h_result), drv.In(h_a), numpy.uint32(SIZE),
block=(n_threads,1,1), grid=(NUM_BIN,1),shared= 256*4)
end.record()
end.synchronize()
secs = start.time_till(end)*1e-3
print("Time for Calculating Histogram on GPU with shared memory")
print("%fs" % (secs))
plt.stem(h_result)
plt.xlim([0,256])
plt.title("Histogram on GPU")
start = cv2.getTickCount()
hist = cv2.calcHist([h_img],[0],None,[256],[0,256])
end = cv2.getTickCount()
time = (end - start)/ cv2.getTickFrequency()
print("Time for Calculating Histogram using OpenCV")
print("%fs" % (secs))
8位图像的容器数量是256,因此我们定义的共享内存大小等于一个块中的线程数量,该数量等于容器数量。计算当前线程块的直方图,先将共享内存初始化为零,并按照前面讨论的方法计算该块的直方图。但这次,结果存储在共享内存中而不是全局内存中。在这种情况下,只有256个线程试图访问共享内存中的256个内存元素,而不是前一代码中的所有65536个元素。这将有助于减少原子操作中的开销时间。最后一行中的最后一个原子添加项将向总体直方图的值添加一个块的直方图。由于加法满足交换律,因此我们不必担心每个块的执行顺序。
代码几乎与上一节中的代码相同,唯一的区别在于内核函数的调用。调用内核时需定义共享内存的大小,可以使用内核调用函数中的共享参数来指定,这里指定为256*4,因为共享内存的大小为256个整数元素,每个元素需要4个字节的存储空间。
OpenCV 以 BGR格式捕获和存储图像,其中蓝色是第一个通道,绿色和红色紧随其后。由BGR格式图像转换成灰度图像的公式如下:
gray =0.299r +0.587g +0.114*b
这里的r,g,b分别代表特定像点的红/绿/蓝通道的强度值
import pycuda.autoinit
import pycuda.driver as drv
from pycuda.compiler import SourceModule
import numpy as np
import cv2
mod = SourceModule \
(
"""
#include
#define INDEX(a, b) a*256+b
__global__ void bgr2gray(float *d_result,float *b_img, float *g_img, float *r_img)
{
unsigned int idx = threadIdx.x+(blockIdx.x*(blockDim.x*blockDim.y));
unsigned int a = idx/256;
unsigned int b = idx%256;
d_result[INDEX(a, b)] = (0.299*r_img[INDEX(a, b)]+0.587*g_img[INDEX(a, b)]+0.114*b_img[INDEX(a, b)]);
}
"""
)
h_img = cv2.imread('lena_color.tif', 1)
h_gray = cv2.cvtColor(h_img, cv2.COLOR_BGR2GRAY)
#print a
b_img = h_img[:, :, 0].reshape(65536).astype(np.float32)
g_img = h_img[:, :, 1].reshape(65536).astype(np.float32)
r_img = h_img[:, :, 2].reshape(65536).astype(np.float32)
h_result = r_img
bgr2gray = mod.get_function("bgr2gray")
bgr2gray(drv.Out(h_result), drv.In(b_img), drv.In(g_img),drv.In(r_img),block=(1024, 1, 1), grid=(64, 1, 1))
h_result=np.reshape(h_result,(256,256)).astype(np.uint8)
cv2.imshow("Grayscale Image",h_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
这里定义一个小的 INDEX函数来计算256×256大小的二维图像的特定索引值,以三通道彩色图像的展平图像数组作为内核函数的输入,其输出是相同大小的灰度图像。INDEX函数用于将线程索引转换为图像中的特定像素位置,然后使用所示函数计算该位置的灰度值。
使用OpenCV的 imread 函数读取彩色图像,图像大小应为256×256,如果不是,则应使用cv2.resize函数将其转换为该大小。由于彩色图像以BGR格式存储,因此使用Python中的数组切片功能将蓝色、绿色和红色通道分别抽离。这些数组是展平的,以便可以传递给内核函数。
调用内核函数时,将三个颜色通道作为输入,并使用一个数组存储输出的灰度图像。内核函数将计算每个像素位置的灰度值,并返回灰度图像的展平数组,这个结果数组使用numpy库中的reshape函数转换回原始图像大小。OpenCV的 imshow函数需要一个无符号整数数据类型来显示图像,以便数组也转换为uint8数据类型。
import pycuda.autoinit
import pycuda.driver as drv
from pycuda.compiler import SourceModule
import numpy as np
import cv2
mod = SourceModule \
(
"""
__global__ void add_num(float *d_result, float *d_a, float *d_b,int N)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
while (tid < N)
{
d_result[tid] = d_a[tid] + d_b[tid];
if(d_result[tid]>255)
{
d_result[tid]=255;
}
tid = tid + blockDim.x * gridDim.x;
}
}
"""
)
img1 = cv2.imread('cameraman.tif', 0)
img2 = cv2.imread('circles.png', 0)
#print a
h_img1 = img1.reshape(65536).astype(np.float32)
h_img2 = img2.reshape(65536).astype(np.float32)
N = h_img1.size
h_result=h_img1
add_img = mod.get_function("add_num")
add_img(drv.Out(h_result), drv.In(h_img1), drv.In(h_img2),np.uint32(N),block=(1024, 1, 1), grid=(64, 1, 1))
h_result=np.reshape(h_result,(256,256)).astype(np.uint8)
cv2.imshow("Image after addition",h_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
import pycuda.autoinit
import pycuda.driver as drv
import numpy as np
import cv2
import pycuda.gpuarray as gpuarray
img = cv2.imread('circles.png',0)
#print a
h_img = img.reshape(65536).astype(np.float32)
d_img = gpuarray.to_gpu(h_img)
d_result = 255- d_img
h_result = d_result.get()
h_result=np.reshape(h_result,(256,256)).astype(np.uint8)
cv2.imshow("Image after Inversion",h_result)
cv2.waitKey(0)
cv2.destroyAllWindows()