CUDA 并行算法Scan、Reduce 图像直方图均衡

1、并行算法介绍

Reduce:

对于一般可以加法运算,如进行8个数相加:


串行计算

由于对于符合交换律,其运算顺序可以改变,可以使用并行计算,如下:


并行计算

对比串行运算,与并行运算的速度:

运算量 8 ... N
并行Step 7 ... N-1
串行Step 3 ... log2(N)

对于并行计算的实现,可参照udacity的课程GitHub:reduce.cu

Scan

Scan 可称为prefix sum,即求前N项(或N-1项)的和,如下:

prefix sum

串行计算流程如上求和无太大差异,而并行计算起来相当讲技巧,以N=8举例:
黑\红实线:加法 黑虚线:赋值

详细可阅读:Parallel prefix sum (scan) with CUDA

2、图像的直方图均衡

假设存在8个灰度级的图像如下图,那么它实际可见的范围为1~4

1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4

频数图如下:


image.png
histogram_equalize.png

即原来的灰度级为1映射后为0,2映射后为2,3映射后为3,4映射后为5
均衡后新灰度频数表如下:

灰度级 0 1 2 3 4 5 6 7
频数 4 0 4 4 0 4 0 0
image.png

映射结果:

0 2 3 5
0 2 3 5
0 2 3 5
0 2 3 5

总的来说没有整个过程没有改变像素点灰度值点之间大小、数量关系,或者说将可视区间拉伸,让色彩对比度更加明显。

3、并行图像直方图均衡化

1)统计每个灰度级像素的数目

__global__ void  histogram_init(char *src,unsigned int *out)
//针对512*512大小灰度图 开启512*512线程,计算256个灰度级相应频数

2)应用Parallel Scan(prefix sum) 计算出映射关系

__global__ void scan_downsweep(unsigned int *in,unsigned int *out,int n)
//输入256点频数表,输出均衡化后256对应得映射值

3)将原图按照映射关系映射到结果图

__global__ void equal_remap(char *img,char *out,unsigned int *idetity)
//根据映射表,重新映射图像

程序效果图如下:


histogram_result

源代码:

#include "cuda.h"
#include "cuda_runtime.h"
#include "cuda_runtime_api.h"
#include "device_functions.h"

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include "stdio.h"
#include <vector>
using namespace std;
using namespace cv;
#define IMAGE_DIR "/home/dzqiu/Documents/image/chaozhou.JPG"

__global__ void  histogram_init(char *src,unsigned int *out)
{
    int x = threadIdx.x + blockDim.x * blockIdx.x;
    int y = threadIdx.y + blockDim.y * blockDim.y;
    int offset = x + y * gridDim.x * blockDim.x;

    unsigned char value = src[offset];
    //原子操作,否则会出错。
    atomicAdd(&out[value],1);
}

//refer: Parallel Prefix Sum (Scan) with CUDA, Mark Harris, April 2007
//link : http: //developer.download.nvidia.com/compute/cuda/2_2/sdk/website/projects/scan/doc/scan.pdf
__global__ void scan_downsweep(unsigned int *in,unsigned int *out,int n)
{
    int Idx = threadIdx.x ;
     extern __shared__ float sdata[];
    sdata[Idx] = in[Idx];
    __syncthreads();

    unsigned int offset=1;

    for(unsigned int i=n>>1;i>0;i>>=1)  //build sum iin place up the tree
    {
        __syncthreads();
        if(Idx<i)
        {
            int ai = offset*(2*Idx+1)-1;
            int bi = offset*(2*Idx+2)-1;
            sdata[bi] += sdata[ai];
        }
        offset *= 2 ;
    }

    if(Idx==0) sdata[n-1]=0;            //travers down tree &build scan
    for(unsigned int i=1;i<n;i<<=1)
    {
        offset >>= 1;
        __syncthreads();
        if(Idx<i)
        {
            int ai = offset*(2*Idx+1)-1;
            int bi = offset*(2*Idx+2)-1;

            int tmp    =sdata[ai];
            sdata[ai]  =   sdata[bi];
            sdata[bi] +=   tmp;
        }
    }
    __syncthreads();
    //out[Idx]=sdata[Idx];
    out[Idx]=((float)sdata[Idx]/512/512*256)-1;
}
__global__ void equal_remap(char *img,char *out,unsigned int *idetity)
{
    int x = threadIdx.x+blockIdx.x*blockDim.x;
    int y = threadIdx.y+blockIdx.y*blockDim.y;
    int offset = x+y*blockDim.x*gridDim.x;

    unsigned char value = img[offset];

    out[offset] = (unsigned char) idetity[value];


}

#define DIM             512
#define GRAY_IDENTITY   256
int main(int argc,char** argv)
{

    Mat img_src = imread(IMAGE_DIR);
    Mat img_gray;
    cvtColor(img_src,img_gray,CV_RGB2GRAY);
    resize(img_gray,img_gray,Size(DIM,DIM));

    unsigned int *dev_histogram;
    cudaMalloc((void**)&dev_histogram,GRAY_IDENTITY*sizeof(int));
    cudaMemset(dev_histogram,0,GRAY_IDENTITY*sizeof(int));

    /*build the histogram of the image*/
    char *dev_gray;
    cudaMalloc((void**)&dev_gray,DIM*DIM);
    cudaMemcpy(dev_gray,img_gray.data,DIM*DIM,cudaMemcpyHostToDevice);
    dim3 blocks(DIM/16,DIM/16);dim3 threads(16,16);
    histogram_init<<<blocks,threads>>>(dev_gray,dev_histogram);
    int host_histogram[GRAY_IDENTITY]={0};
    cudaMemcpy(host_histogram,dev_histogram,GRAY_IDENTITY*sizeof(int),cudaMemcpyDeviceToHost);

    /*equalize the histogram*/
    unsigned int *dev_histogram_equal;
    cudaMalloc((void**)&dev_histogram_equal,GRAY_IDENTITY*sizeof(int));
    blocks=dim3(1,1);threads=dim3(GRAY_IDENTITY,1);
    scan_downsweep<<<blocks,threads,sizeof(int)*GRAY_IDENTITY>>>(dev_histogram,dev_histogram_equal,GRAY_IDENTITY);
    int host_histogram_equal[GRAY_IDENTITY]={0};
    cudaMemcpy(host_histogram_equal,dev_histogram_equal,GRAY_IDENTITY*sizeof(int),cudaMemcpyDeviceToHost);

    for(int i=0;i<GRAY_IDENTITY/4;i++)
    {
       //if(host_histogram[i]!=0)
         printf("bin[%3d]:%6d -> %6d| bin[%3d]:%6d -> %6d| bin[%3d]:%6d -> %6d| bin[%3d]:%6d -> %6d\n",
                4*i,host_histogram[4*i],host_histogram_equal[4*i],
                4*i+1,host_histogram[4*i+1],host_histogram_equal[4*i+1],
                4*i+2,host_histogram[4*i+2],host_histogram_equal[4*i+2],
                4*i+3,host_histogram[4*i+3],host_histogram_equal[4*i+3]);
    }

    /*remap the image use the equalized histogram*/
    char *dev_equalImg;
    cudaMalloc((void**)&dev_equalImg,DIM*DIM);
    blocks=dim3(DIM/16,DIM/16);threads=dim3(16,16);
    equal_remap<<<blocks,threads>>>(dev_gray,dev_equalImg,dev_histogram_equal);
    Mat equalImg(DIM,DIM,CV_8UC1,Scalar(255));
    cudaMemcpy(equalImg.data,dev_equalImg,DIM*DIM,cudaMemcpyDeviceToHost);




    cudaFree(dev_gray);
    cudaFree(dev_histogram);
    cudaFree(dev_histogram_equal);
    imshow("gray_img",img_gray);
    imshow("histogram equalization use GPU",equalImg);

    waitKey(0);
    return 0;
}

源码下载:GitHub
参考:Parallel Prefix Sum (Scan) with CUDA, Mark Harris, April 2007

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • 直方图变换 灰度变换 点运算 几何变换 直方图变换 1.灰度直方图 灰度直方图:数字图像中每一灰度级像素出现的频次...
    hyfine阅读 4,647评论 0 0
  • 先来看看直方图均衡化的效果图。 直观感受,均衡化后的图像明暗对比更明显。亮的地方更亮,暗的地方更暗,拉开了差距。1...
    ck2016阅读 10,065评论 0 4
  • 图像直方图(英语:Image Histogram)是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像...
    fengzhizi715阅读 9,965评论 1 6
  • 他和她的未尽之言 可以是一种特殊的香气 流下你的眼泪 虽然心是干涸 遥远的思念 在诗人笔下低沉婉转 似乎都流转归来...
    秋空积雨阅读 280评论 0 0
  • 【三件事】 1,帮婶婶修电脑 2,keep 3/100 3,懂你课程1/100 【小确幸】 思路变清晰 【感悟】 ...
    Sabrina和向日葵阅读 254评论 0 0