前言
幾乎絕大部分圖像操作的前置處理都會(huì)把rgb彩色圖轉(zhuǎn)為灰度圖,然后再進(jìn)行二值化操作等等。opencv讀取圖像,一般都是彩色圖,即包含bgr三通道,而灰度圖則只包含一個(gè)通道,只含亮度信息,不含色彩信息的圖象,就象我們平時(shí)看到的黑白照片:亮度由暗到明,變化是連續(xù)的。因此,要表示灰度圖,就需要把亮度值進(jìn)行量化。通常劃分成0到255共256個(gè)級(jí)別,其中0最暗(全黑),255最亮(全白)。在表示顏色的方法中,除了RGB外,還有一種叫YUV的表示方法,應(yīng)用也很多。電視信號(hào)中用的就是一種類(lèi)似于YUV的顏色表示方法。在這種表示方法中,Y分量的物理含義就是亮度,Y分量包含了灰度圖的所有信息,只用Y分量就能完全能夠表示出一幅灰度圖來(lái)。
彩色圖轉(zhuǎn)灰度圖方法
一般而言,將彩色圖轉(zhuǎn)換為灰度圖有以下幾種方法:
(1)最大值法
將彩色圖像中每個(gè)像素的rgb分量中亮度的最大值作為灰度圖的灰度值
(2)平均值法
將彩色圖像中的每個(gè)像素的rgb三分量求平均得到一個(gè)灰度圖的灰度值
(3)加權(quán)平均法
由于人眼對(duì)紅綠藍(lán)三種顏色的敏感程度不同,在灰度轉(zhuǎn)換時(shí),每個(gè)顏色分配的權(quán)重也是不同的,由此得到色彩心理學(xué)公式:
Gray=0.299red+0.587green+0.114*blue
第三種的加權(quán)平均法也是使用最為廣泛的方法,本次試驗(yàn)也是使用這個(gè)方法
cuda試驗(yàn)
還是首先上代碼
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <cuda.h>
#include <device_functions.h>
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
//輸入圖像為BGR圖,將其轉(zhuǎn)化為gray圖
__global__ void rgb2grayInCuda(uchar3* dataIn, unsigned char* dataOut, int imgHeight, int imgWidth)
{
int xIndex = threadIdx.x + blockIdx.x * blockDim.x;
int yIndex = threadIdx.y + blockIdx.y * blockDim.y;
if (xIndex < imgWidth && yIndex < imgHeight)
{
uchar3 rgb = dataIn[yIndex * imgWidth + xIndex];
dataOut[yIndex * imgWidth + xIndex] = 0.299f * rgb.x + 0.587f * rgb.y + 0.114f * rgb.z;
}
}
int main()
{
Mat srcImg = imread("D:\\work\\code\\blog\\bin\\win64\\Release\\20140702104508726.jpg");//輸入圖片
int imgHeight = srcImg.rows;
int imgWidth = srcImg.cols;
Mat grayImg(imgHeight, imgWidth, CV_8UC1, Scalar(0));//輸出灰度圖
double time1 = static_cast<double>(cv::getTickCount());
cvtColor(srcImg, grayImg, CV_BGR2GRAY);
double time2 = static_cast<double>(cv::getTickCount());
std::cout << "cpu Time use: " << 1000 * (time2 - time1) / cv::getTickFrequency() << "ms" << std::endl;//輸出運(yùn)行時(shí)間
cv::imwrite("gray_cpu.jpg", grayImg);
//在GPU中開(kāi)辟輸入輸出空間
uchar3* d_in;
unsigned char* d_out;
int* d_hist;
cudaMalloc((void**)&d_in, imgHeight * imgWidth * sizeof(uchar3));
cudaMalloc((void**)&d_out, imgHeight * imgWidth * sizeof(unsigned char));
//將圖像數(shù)據(jù)傳入GPU中
cudaMemcpy(d_in, srcImg.data, imgHeight * imgWidth * sizeof(uchar3), cudaMemcpyHostToDevice);
dim3 threadsPerBlock(32, 32);
dim3 blocksPerGrid((imgWidth + threadsPerBlock.x - 1) / threadsPerBlock.x, (imgHeight + threadsPerBlock.y - 1) / threadsPerBlock.y);
//記錄起始時(shí)間
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);
//灰度化
rgb2grayInCuda << <blocksPerGrid, threadsPerBlock >> > (d_in, d_out, imgHeight, imgWidth);
//將數(shù)據(jù)從GPU傳回CPU
cudaMemcpy(grayImg.data, d_out, imgHeight * imgWidth * sizeof(unsigned char), cudaMemcpyDeviceToHost);
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("gpu time cost: %3.5f ms", elapsedTime);
cv::imwrite("gray_gpu.jpg", grayImg);
cudaFree(d_in);
cudaFree(d_out);
cudaFree(d_hist);
return 0;
}
得出的cpu和gpu的灰度化圖片肉眼可見(jiàn)的有些許差別,詳細(xì)見(jiàn)下圖


時(shí)間的測(cè)試如下:

基本上cpu所耗時(shí)間是gpu的21倍左右
對(duì)比cpu和gpu的輸出的灰度圖,可以發(fā)現(xiàn),gpu的版本會(huì)略微暗一些,所以猜測(cè)opencv不是直接用的這個(gè)公式
Gray=0.299red+0.587green+0.114*blue
經(jīng)過(guò)查閱,發(fā)現(xiàn)還有其他的灰度圖計(jì)算公式,比如為了避免浮點(diǎn)運(yùn)算,將上面的公式改為:
Gray=(30red+59green+11*blue + 50 )/ 100
將代碼改成這個(gè)整數(shù)計(jì)算公式后,輸出的gpu和cpu的灰度圖則完全一模一樣
最終gpu版本計(jì)算灰度值的代碼改為(這里要注意opencv讀取的圖片,通道順序?yàn)锽GR):
dataOut[yIndex * imgWidth + xIndex] = (11 * rgb.x+ 59 * rgb.y + 30 * rgb.z + 50)/100;
最終輸出效果圖為:


參考
【圖像筆記】RGB圖像轉(zhuǎn)灰度圖像
RGB轉(zhuǎn)化灰度圖公式
CUDA精進(jìn)之路(三):圖像處理——圖像灰度化、灰度直方圖統(tǒng)計(jì)