作者:翟天保Steven
版權(quán)聲明:著作權(quán)歸作者所有,商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處
實現(xiàn)原理
? ? ? ?歸一化算法的核心目的是將圖像中的像素值調(diào)整到一個統(tǒng)一的范圍,通常是[0, 255]。算法步驟如下:
- 找到圖像中的最大值和最小值。在CUDA實現(xiàn)中,首先通過并行核函數(shù)getMaxMinValue_CUDA,利用atomicMax和atomicMin原子操作來確保線程安全地計算出最大和最小像素值。
- 在核函數(shù) normalizeImage_CUDA中進行歸一化處理。該過程通過將每個像素值減去最小值并除以最大值與最小值之差,最后乘以255,來線性映射像素值到新范圍。
? ? ? ?在圖像處理中,歸一化可以增強圖像對比度,提高圖像處理算法的穩(wěn)定性和魯棒性。通過將像素值調(diào)整到統(tǒng)一范圍,可以更好地提取特征,促進后續(xù)的處理步驟,如邊緣檢測或分類。
? ? ? ?本文將通過一個實戰(zhàn)案例,進行歸一化的展示。
? ? ? ?(注意本文案例基于OpenCV實現(xiàn),但是對CUDA而言,核心部分與OpenCV無關(guān),可根據(jù)自身場景和數(shù)據(jù)結(jié)構(gòu)進行更改。)
C++測試代碼
ImageProcessing.cuh
#pragma once
#include <cuda_runtime.h>
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#include <device_launch_parameters.h>
using namespace cv;
using namespace std;
#define TILE_WIDTH 16
// 預(yù)準(zhǔn)備過程
void warmupCUDA();
// 圖像歸一化-CPU
cv::Mat normalizeImage_CPU(cv::Mat input);
// 圖像歸一化-GPU
cv::Mat normalizeImage_GPU(cv::Mat input);
ImageProcessing.cu
#include "ImageProcessing.cuh"
// 預(yù)準(zhǔn)備過程
void warmupCUDA()
{
float* dummy_data;
cudaMalloc((void**)&dummy_data, sizeof(float));
cudaFree(dummy_data);
}
// 圖像歸一化-CPU
cv::Mat normalizeImage_CPU(cv::Mat input)
{
int row = input.rows;
int col = input.cols;
// 確認(rèn)最值
uchar maxV = 0;
uchar minV = 255;
for (int i = 0; i < row; ++i)
{
for (int j = 0; j < col; ++j)
{
if (input.at<uchar>(i, j) > maxV)
{
maxV = input.at<uchar>(i, j);
}
if (input.at<uchar>(i, j) < minV)
{
minV = input.at<uchar>(i, j);
}
}
}
cout << "max value:" << int(maxV) << endl;
cout << "min value:" << int(minV) << endl;
// 歸一化
cv::Mat result = cv::Mat(row, col, CV_8UC1);
for (int i = 0; i < row; ++i)
{
for (int j = 0; j < col; ++j)
{
result.at<uchar>(i, j) = uchar(float(input.at<uchar>(i, j) - minV) / (maxV - minV) * 255);
}
}
return result;
}
// 獲取最大最小值核函數(shù)
__global__ void getMaxMinValue_CUDA(uchar* inputImage, int width, int height, int *maxV, int *minV)
{
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < height && col < width)
{
atomicMax(maxV, int(inputImage[row * width + col]));
atomicMin(minV, int(inputImage[row * width + col]));
}
}
// 圖像歸一化核函數(shù)
__global__ void normalizeImage_CUDA(uchar* inputImage, uchar* outputImage, int width, int height, uchar maxV, uchar minV)
{
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < height && col < width)
{
outputImage[row * width + col] = uchar(float(inputImage[row * width + col] - minV) / (maxV - minV) * 255);
}
}
// 圖像歸一化-GPU
cv::Mat normalizeImage_GPU(cv::Mat input)
{
int row = input.rows;
int col = input.cols;
// 定義計時器
float spendtime = 0.0f;
cudaEvent_t start, end;
cudaEventCreate(&start);
cudaEventCreate(&end);
// 分配GPU內(nèi)存
uchar* d_inputImage, *d_outputImage;
cudaMalloc(&d_inputImage, row * col * sizeof(uchar));
cudaMalloc(&d_outputImage, row * col * sizeof(uchar));
// 將輸入圖像數(shù)據(jù)從主機內(nèi)存復(fù)制到GPU內(nèi)存
cudaMemcpy(d_inputImage, input.data, row * col * sizeof(uchar), cudaMemcpyHostToDevice);
// 計算塊和線程的大小
dim3 blockSize(TILE_WIDTH, TILE_WIDTH);
dim3 gridSize((col + blockSize.x - 1) / blockSize.x, (row + blockSize.y - 1) / blockSize.y);
// 求最值
int h_maxValue = 0;
int h_minValue = 255;
int *d_maxValue;
int *d_minValue;
cudaMalloc((void**)&d_maxValue, sizeof(int));
cudaMalloc((void**)&d_minValue, sizeof(int));
cudaMemcpy(d_maxValue, &h_maxValue, sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_minValue, &h_minValue, sizeof(int), cudaMemcpyHostToDevice);
getMaxMinValue_CUDA << <gridSize, blockSize >> > (d_inputImage, col, row, d_maxValue, d_minValue);
cudaMemcpy(&h_maxValue, d_maxValue, sizeof(int), cudaMemcpyDeviceToHost);
cudaMemcpy(&h_minValue, d_minValue, sizeof(int), cudaMemcpyDeviceToHost);
cout << "max value:" << h_maxValue << endl;
cout << "min value:" << h_minValue << endl;
// 歸一化
cudaEventRecord(start, 0);
normalizeImage_CUDA << <gridSize, blockSize >> > (d_inputImage, d_outputImage, col, row, h_maxValue, h_minValue);
cudaEventRecord(end, 0);
cudaEventSynchronize(end);
cudaEventElapsedTime(&spendtime, start, end);
cout << "kernel_CUDA spend time:" << spendtime << endl;
// 將處理后的圖像數(shù)據(jù)從GPU內(nèi)存復(fù)制回主機內(nèi)存
cv::Mat output(row, col, CV_8UC1);
cudaMemcpy(output.data, d_outputImage, row * col * sizeof(uchar), cudaMemcpyDeviceToHost);
// 清理GPU內(nèi)存
cudaFree(d_inputImage);
cudaFree(d_outputImage);
return output;
}
main.cpp
#include "ImageProcessing.cuh"
void main()
{
// 預(yù)準(zhǔn)備
warmupCUDA();
cout << "normalizeImage test begin." << endl;
// 加載
cv::Mat src = imread("test pic/test5.jpg", 0);
// 調(diào)整數(shù)據(jù)區(qū)間
cv::Mat src2;
cv::normalize(src, src2, 20, 230, NORM_MINMAX);
// CPU版本
clock_t s1, e1;
s1 = clock();
cv::Mat output1 = normalizeImage_CPU(src2);
e1 = clock();
cout << "CPU time:" << double(e1 - s1) << "ms" << endl;
// GPU版本
clock_t s2, e2;
s2 = clock();
cv::Mat output2 = normalizeImage_GPU(src2);
e2 = clock();
cout << "GPU time:" << double(e2 - s2) << "ms" << endl;
// 查看輸出
cv::Mat test1 = output1.clone();
cv::Mat test2 = output2.clone();
cout << "normalizeImage test end." << endl;
}
測試效果?
? ? ? ?在本文案例中,我通過OpenCV自帶的歸一化函數(shù)將圖像的最值設(shè)為20和230,如圖1所示,圖像對比度有所下降,然后用自寫的歸一化算法再將其調(diào)整后回0-255,圖像對比度明顯提升。速度方面,歸一化核函數(shù)僅用了0.17ms,大多耗時還是在CPU和GPU的數(shù)據(jù)傳輸上。
? ? ? ?如果函數(shù)有什么可以改進完善的地方,非常歡迎大家指出,一同進步何樂而不為呢~
? ? ? ?如果文章幫助到你了,可以點個贊讓我知道,我會很快樂~加油!