求助:CUDA Npp库nppiFilterGauss_8u_C1R函数使用异常排查
问题描述
尝试使用CUDA的Npp库调用nppiFilterGauss_8u_C1R实现图像高斯滤波,但输出图像始终异常(纯黑、纯灰或杂乱条纹)。输入为512*512的Lena灰度图,核心代码如下:
void NppGaussianFilter(std::string strSrc, std::string strDst) { // Convert the image to gray scale image cv::Mat img = cv::imread(strSrc); if (img.empty()) { std::cerr << "Failed to load image: " << strSrc << std::endl; return; } cv::Mat grayImg; cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY); // Some image parameters int nWidth = grayImg.cols; int nHeight = grayImg.rows; int nChannels = grayImg.channels(); Npp8u nStep = grayImg.step[0]; size_t sizeToCopy = nWidth * nHeight * nChannels * sizeof(Npp8u); // Allocate memory of source image pointer on device and copy image data from host to device Npp8u* pSrc_dev = nullptr; cudaError_t err = cudaMalloc((void**)&pSrc_dev, sizeToCopy); if (err != cudaSuccess) { std::cerr << "Failed to allocate device memory for pSrc_dev" << std::endl; return; } cudaMemcpy(pSrc_dev, grayImg.data, sizeToCopy, cudaMemcpyHostToDevice); // Allocate memory of destination image pointer on device Npp8u* pDst_dev = nullptr; err = cudaMalloc((void**)&pDst_dev, sizeToCopy); if (err != cudaSuccess) { std::cerr << "Failed to allocate device memory for pDst_dev" << std::endl; cudaFree(pSrc_dev); return; } // Implement the gauss filter function NppiMaskSize eMaskSize = NPP_MASK_SIZE_3_X_3; NppiSize roiSize = { nWidth, nHeight }; nppiFilterGauss_8u_C1R(pSrc_dev, nStep, pDst_dev, nStep, roiSize, eMaskSize); // Copy image data from device to host cv::Mat newImg(nHeight, nWidth, CV_8UC1); cudaMemcpy(newImg.data, pDst_dev, sizeToCopy, cudaMemcpyDeviceToHost); cv::imwrite(strDst, newImg); cudaFree(pSrc_dev); cudaFree(pDst_dev); }
问题排查与修正
以下是代码中的关键错误及修复方案:
1. 缺失NPP函数错误码检查
nppiFilterGauss_8u_C1R返回NppStatus类型的错误码,未检查该值会导致无法定位函数调用失败的原因(比如ROI越界、步长不匹配等),必须在调用后校验返回值。
2. 图像边界空间不足
NPP滤波函数要求源图像的ROI周围预留掩码半径大小的边界空间,否则处理边缘像素时会越界访问内存,导致输出异常。以3x3掩码为例,需在源图像上下左右各预留1个像素的边界。
3. 步长类型不匹配
grayImg.step[0]是size_t类型,而NPP函数的步长参数要求为Npp32s(32位整数)。使用Npp8u存储步长会导致步长值被截断(步长超过255时),引发内存访问错误。
4. 未同步CUDA操作
CUDA操作默认异步执行,函数调用后直接拷贝数据可能导致设备未完成计算,主机端拿到未初始化的脏数据,需在拷贝前添加同步操作。
修正后的完整代码
#include <opencv2/opencv.hpp> #include <npp.h> #include <iostream> void NppGaussianFilter(std::string strSrc, std::string strDst) { // 加载并转换为灰度图 cv::Mat img = cv::imread(strSrc); if (img.empty()) { std::cerr << "Failed to load image: " << strSrc << std::endl; return; } cv::Mat grayImg; cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY); // 图像参数 const int nWidth = grayImg.cols; const int nHeight = grayImg.rows; const int nChannels = grayImg.channels(); const Npp32s nStep = static_cast<Npp32s>(grayImg.step[0]); // 修正步长类型 const int maskRadius = 1; // 3x3掩码的半径为1 const int srcWidthWithBorder = nWidth + 2 * maskRadius; const int srcHeightWithBorder = nHeight + 2 * maskRadius; const size_t srcSizeWithBorder = srcWidthWithBorder * srcHeightWithBorder * nChannels * sizeof(Npp8u); const size_t dstSize = nWidth * nHeight * nChannels * sizeof(Npp8u); // 分配带边界的源设备内存,并拷贝图像数据(包含边界扩展) Npp8u* pSrc_dev = nullptr; cudaError_t err = cudaMalloc((void**)&pSrc_dev, srcSizeWithBorder); if (err != cudaSuccess) { std::cerr << "Failed to allocate device memory for pSrc_dev: " << cudaGetErrorString(err) << std::endl; return; } // 使用NPP的边界扩展函数将原图像拷贝到带边界的设备内存中 NppiSize roiSize = {nWidth, nHeight}; NppStatus nppErr = nppiCopyReplicateBorder_8u_C1R(grayImg.data, nStep, roiSize, pSrc_dev + maskRadius * srcWidthWithBorder + maskRadius, static_cast<Npp32s>(srcWidthWithBorder), roiSize, maskRadius, maskRadius); if (nppErr != NPP_SUCCESS) { std::cerr << "Failed to replicate border: " << nppErr << std::endl; cudaFree(pSrc_dev); return; } // 分配目标设备内存 Npp8u* pDst_dev = nullptr; err = cudaMalloc((void**)&pDst_dev, dstSize); if (err != cudaSuccess) { std::cerr << "Failed to allocate device memory for pDst_dev: " << cudaGetErrorString(err) << std::endl; cudaFree(pSrc_dev); return; } // 执行高斯滤波 NppiMaskSize eMaskSize = NPP_MASK_SIZE_3_X_3; nppErr = nppiFilterGauss_8u_C1R(pSrc_dev + maskRadius * srcWidthWithBorder + maskRadius, static_cast<Npp32s>(srcWidthWithBorder), pDst_dev, nStep, roiSize, eMaskSize); if (nppErr != NPP_SUCCESS) { std::cerr << "Gaussian filter failed: " << nppErr << std::endl; cudaFree(pSrc_dev); cudaFree(pDst_dev); return; } // 同步并拷贝结果到主机 err = cudaDeviceSynchronize(); if (err != cudaSuccess) { std::cerr << "CUDA sync failed: " << cudaGetErrorString(err) << std::endl; cudaFree(pSrc_dev); cudaFree(pDst_dev); return; } cv::Mat newImg(nHeight, nWidth, CV_8UC1); err = cudaMemcpy(newImg.data, pDst_dev, dstSize, cudaMemcpyDeviceToHost); if (err != cudaSuccess) { std::cerr << "Failed to copy data from device to host: " << cudaGetErrorString(err) << std::endl; cudaFree(pSrc_dev); cudaFree(pDst_dev); return; } cv::imwrite(strDst, newImg); // 释放内存 cudaFree(pSrc_dev); cudaFree(pDst_dev); }
内容的提问来源于stack exchange,提问作者Yanqi Huo
相关产品推荐
相关产品推荐

