空间域指图像平面本身,这类图像处理方法直接以图像中的像素操作为基础。相对于变换域中的图像处理而言,变化域的图像处理首先把一幅图像变换到变换域,在变换域中进行处理,然后通过反变换把处理结果返回到空间域。空间域处理主要分为灰度变换和空间滤波两类。灰度变换在图像的单个像素上操作,主要以对比度和阈值处理为目的。空间滤波(模糊技术)涉及改善性能的操作,如通过图像中每一个像素的邻域处理来锐化图像。
-
灰度变换和空间滤波基础,空间域处理公式:g(x, y) = T[f(x, y)],其中f(x, y)是输入图像,g(x, y)是处理后的图像,T是在点(x, y)的邻域上定义的关于f的一种算子。算子可应用于单幅图像或图像集合。
-
一些基本的灰度变换函数:灰度变换是所有图像处理技术中最简单的技术。r 和 s 分别代表处理前后的像素值,这些值与s = T®表达式的形式有关,其中T是把像素值 r 映射到像素值 s 的一种变化。图像增强常用的三类基本函数:线性函数(反转和恒等变换)、对数函数(对数和反对数变换)和幂律函数(n次幂和n次根变换)。恒等函数是最一般的情况,其输出灰度等于输入灰度的变换。
- 图像反转:反转变换,可得到灰度级范围为[0, L-1]的一幅图像的反转图像,该反转图像由下式给出:s = L - 1 - r。这种方式反转一幅图像的灰度级,可得到等效的照片底片。
Mat ImageTransform::ImageGrayReversal(Mat & originData) { int imageH = originData.rows; int imageW = originData.cols; int imageC = originData.channels(); Mat targetData = Mat::zeros(imageH, imageW, CV_8UC1); for (int row = 0; row < imageH; row++) { for (int col = 0; col < imageW; col++) { if (imageC == 3) { // 源图像灰度化 gray = 0.144*B + 0.587*G + 0.299*R double grayValue = (double)0.144*originData.at<Vec3b>(row, col)[0] + 0.587*originData.at<Vec3b>(row, col)[1] + 0.299*originData.at<Vec3b>(row, col)[2]; // 图像反转操作 targetData.at<uchar>(row, col) = (uchar)255 - grayValue; } else if (imageC == 1) { // 图像反转操作 targetData.at<uchar>(row, col) = (uchar)255 - originData.at<uchar>(row, col); } } } return targetData; }
- 对数变换:s = c log(1 + r), 其中c是一个常数,并假设r >= 0。如图对数曲线的形状表明,该变换将输入中范围较窄的低灰度值映射为输出中较宽范围的灰度值。这种类型的变换来扩展图像中的暗像素的值,同时压缩更高灰度级的值。反对数变化的作用与此相反。
Mat ImageTransform::ImageLogTransform(Mat & originData, float c) { if (originData.empty()) { return Mat(); } CV_Assert(originData.channels() == 3); Mat targetData = Mat::zeros(originData.size(), originData.type()); //图像类型转换 originData.convertTo(targetData, CV_32F); for (int row = 0; row < originData.rows; row++) { for (int col = 0; col < originData.cols; col++) { // 对数变换 s = c*log(1+r) targetData.at<Vec3f>(row, col)[0] = c * log(1 + originData.at<Vec3b>(row, col)[0]); targetData.at<Vec3f>(row, col)[1] = c * log(1 + originData.at<Vec3b>(row, col)[1]); targetData.at<Vec3f>(row, col)[2] = c * log(1 + originData.at<Vec3b>(row, col)[2]); } } // 归一化到0~255 normalize(targetData, targetData, 0, 255, CV_MINMAX); // 转换成8bit图像显示 convertScaleAbs(targetData, targetData); return targetData; }
- 幂律(伽马)变换:幂律变换的基本形式为:,其中c 和 r(伽马)为正常数。s与r(伽马)的关系曲线如图,与对数变换的情况类似,部分r(伽马)值的幂律曲线将较窄范围的暗色输入值映射为较宽范围的输出值,相反地,对于输入高灰度级值时也成立。与对数函数不同的是,随着r(伽马)的变化,将简单地得到一族可能的变换曲线。r(伽马)>1的值所生成的曲线和r(伽马)r<1的值所生成的曲线的效果完全相反。在c=r(伽马)=1时简化成恒等变换。
Mat ImageTransform::ImageGammaTransform(Mat & originData, float c, float gamma) { if (originData.empty()) { return Mat(); } CV_Assert(originData.channels() == 3); Mat targetData = Mat::zeros(originData.size(), originData.type()); //图像类型转换 originData.convertTo(targetData, CV_32F); for (int row = 0; row < originData.rows; row++) { for (int col = 0; col < originData.cols; col++) { // 对数变换 s = c*log(1+r) targetData.at<Vec3f>(row, col)[0] = c * pow((double)originData.at<Vec3b>(row, col)[0], gamma); targetData.at<Vec3f>(row, col)[1] = c * pow((double)originData.at<Vec3b>(row, col)[1], gamma);; targetData.at<Vec3f>(row, col)[2] = c * pow((double)originData.at<Vec3b>(row, col)[2], gamma);; } } // 归一化到0~255 normalize(targetData, targetData, 0, 255, CV_MINMAX); // 转换成8bit图像显示 convertScaleAbs(targetData, targetData); return targetData; }
- 分段线性变换函数:优点-分段线性函数的形式可以是任意复杂的,缺点-它的技术说明要求用户输入。
- **对比度拉伸:**最简单的分段线性函数,是扩展图像灰度级动态范围的处理,它可以跨越记录介质和显示装置的全部灰度范围。
- 灰度级分层:突出图像中特定灰度范围的亮度通常是重要的,其应用包括增强特征。
- 比特平面分层:像素是由比特组成的数字。把一幅图像分解为比特平面(一幅8比特图像可考虑为由8个1比特平面组成),对于分析图像中每个比特的相对重要性是很有用的,这一处理可以帮助我们确定用于量化该图像的比特数的充分性,这种类型的分解对图像压缩也很有用。重建一幅图像时所用的平面要比全部平面少,重建是使用第n个平面的像素乘以常熟2^n-1来完成的。
- 图像反转:反转变换,可得到灰度级范围为[0, L-1]的一幅图像的反转图像,该反转图像由下式给出:s = L - 1 - r。这种方式反转一幅图像的灰度级,可得到等效的照片底片。
-
直方图处理:灰度级范围为[0, L-1]的数字图像的直方图是离散函数h(r_k) = n_k,其中r_k是第k级灰度值,n_k是图像中灰度为r_k的像素个数。归一化后的直方图由p(r_k) = n_k/(M*N)给出,其中 k=0, 1, …, L-1。简单说,p(r_k)是灰度级r_k在图像中出现的概率的一个估计。归一化直方图的所有分量之和应等于1 。直方图是多种空间处理技术的基础,直方图操作可用于图像增强。暗图像-直方图的分量集中在灰度级的低(暗)端;亮图像-直方图的分量倾向于灰度级的高端;低对比度图像-具有较窄的直方图,集中于灰度级的中部;高对比度图像-直方图的分量覆盖了很宽的灰度级范围,而且像素的分布没有太不均匀,只有少量垂直线比其他的高许多。结论:若一幅图像的像素倾向于占据整个可能的灰度级并且分布均匀,则该图像会有高对比度的外观并展示灰色调的较大变化。
直方图统计C++代码实现// 直方图数据统计 Mat ImageHist::selfCalcHist(Mat imageData, int dims, int * binNums, float ** ranges, int * channels) { Mat hist; if (dims == 1) { // 灰度图像直方图统计(1d) int bin1d = binNums[0]; hist = Mat::zeros(1, bin1d, CV_32FC1); // 每个间隔包含多少个像素 float dRange = ranges[0][1] / (float)bin1d; for (int r = 0; r < imageData.rows; r++) { for (int c = 0; c < imageData.cols; c++) { // 计算每个灰度值像素属于哪个bin,即哪个间隔中(floor-向下取整) int binth = floor(static_cast<float>(imageData.at<uchar>(r, c)) / dRange); // 对应格点统计数据++ hist.at<float>(0, binth)++; } } } else if (dims == 2) { // hsv格式图像h-s二维直方图分析(二维直方图就是两种约束范围,在H情况下,S发生的概率) int hChannelBin = binNums[0], sChannelBin = binNums[1]; hist = Mat::zeros(hChannelBin, sChannelBin, CV_32FC1); // 每个间隔包含多少个像素 float dHRange = ranges[0][1] / (float)hChannelBin, dSRange = ranges[1][1] / (float)sChannelBin; for (int r = 0; r < imageData.rows; r++) { for (int c = 0; c < imageData.cols; c++) { // 计算每个灰度值像素属于哪个bin,即哪个间隔中(floor-向下取整) int hBinth = floor(static_cast<float>(imageData.at<Vec3b>(r, c)[0]) / dHRange); int sBinth = floor(static_cast<float>(imageData.at<Vec3b>(r, c)[1]) / dSRange); // 对应格点统计数据++ hist.at<float>(hBinth, sBinth)++; } } } return hist; }
// 绘制直方图柱 Mat ImageHist::drawHist(Mat hist) { Mat hisImage; int scale = 10; // 求解hist中最大元素 double maxValue = 0; minMaxLoc(hist, 0, &maxValue, 0, 0); Size imageSize = hist.size(); if (imageSize.height == 1) { // 1d 直方图绘制 hisImage = Mat::zeros(256+50, imageSize.width*scale, CV_8UC3); for (int r = 0; r < hist.rows; r++) { for (int c = 0; c < hist.cols; c++) { float binValue = hist.at<float>(r, c); // 归一化到255 int intensity = cvRound(binValue * 255 / maxValue); // 绘制矩形框 //rectangle(hisImage, // Point(c*scale, intensity)/*左上角点坐标*/, // Point((c+1)*scale-1, 0)/*右下角点坐标*/, // Scalar::all(intensity), -1); rectangle(hisImage, Point(c*scale, hisImage.rows - intensity)/*左上角点坐标*/, Point((c + 1)*scale - 1, hisImage.rows)/*右下角点坐标*/, Scalar::all(intensity), -1); } } } else if (imageSize.height > 1) { // 2d 直方图绘制 hisImage = Mat::zeros(imageSize.height*scale, imageSize.width*scale, CV_8UC3); for (int r = 0; r < hist.rows; r++) { for (int c = 0; c < hist.cols; c++) { float binValue = hist.at<float>(r, c); // 归一化到255 int intensity = cvRound(binValue * 255 / maxValue); // 绘制矩形框 rectangle(hisImage, Point(c*scale, r*scale)/*左上角点坐标*/, Point((c + 1)*scale - 1, (r + 1)*scale - 1)/*右下角点坐标*/, Scalar::all(intensity), -1); } } } return hisImage; }
- 直方图均衡 :直方图统计增强最常使用的方法。
直方图均衡导致对比度增强可以补偿图像在视觉上难以区分灰度级的差别,直方图均衡是对比度(即一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,即指一幅图像灰度反差的大小)增强的工具 - 直方图匹配(规定化):直方图均衡是将一幅直方图分布不均匀图像将其直方图均匀化,均匀直方图的基本增强并不是最好的方法。有时候希望处理后的图像具有规定的直方图形状,产生特定的直方图的方法称为直方图匹配或直方图规定化。
r与z通过某种关系变换都可以转换为s,s就相当于r转换到z的中间变量。 - 局部直方图处理
- 在图像增强中使用直方图统计
- 直方图均衡 :直方图统计增强最常使用的方法。
-
空间滤波基础:在图像的空域上对图像进行操作。
- 空间滤波机理:一般来说,使用大小为m x n的滤波器对大小为M x N的图像进行线性空间滤波,可表示为:,其中,x和y是可变的,以便w中的每个像素可以访问f中的每个像素。空间滤波可分为线性空间滤波和非线性空间滤波,它们之间差别其实就是滤波器w的差别。线性空间滤波其实就是w分布为线性的,非线性空间滤波就是w的分布为非线性的。卷积操作的表示:,其中等式右侧的减号表示翻转f(即卷积核旋转180度)。
- 空间滤波器模板的产生:(1)平均灰度:,该式与系数w_i=1/9的卷积操作相同,换句话来说,使用系数为1/9的3x3模板进行线性滤波操作可以实现所希望的平均。(2)高斯函数产生滤波器:,为了从该函数产生一个大小为3x3的滤波器模板,就有w_1 = h(-1, -1), w_2 = h(-1, 0), …, w_9 = h(1, 1),类似方式可以产生一个mxn的滤波器模板。
-
平滑空间滤波器:用于模糊处理和降低噪声,例如在(大)目标提取之前去除图像中的一些琐碎的细节,以及桥接直线或曲线的缝隙(这个好像是有膨胀腐蚀操作更有效)。通过线性滤波和非线性滤波模糊处理,可以降低噪声。统计排序滤波器是一种非线性滤波器,其中的代表就是中值滤波器,中值滤波器对处理脉冲噪声非常有效,这种噪声也被称为椒盐噪声。
-
锐化空间滤波器:锐化处理的主要目的突出灰度的过渡部分。锐化处理可以由**空间微分(求导)**来实现。图像微分增强边缘和其他突变(如噪声),而削弱灰度变化缓慢的区域。
- 基础:一阶微分的基本定义是差分:,二阶微分定义如下差分:。对于图像的一阶、二阶微分的任何定义都必须保证以下几点:(1)恒定灰度区域的微分值为零;(2)一阶微分在灰度台阶或斜坡处微分值非零(二阶微分在灰度台阶或斜坡的起点处微分值非零);(3)沿着斜坡的微分值非零。二阶微分产生由零分开的一个像素宽的双边缘。因此二阶微分在增强细节方面要比一阶微分好得多,这是一个适合锐化图像的理想特性。
- 使用二阶微分进行图像锐化—拉普拉斯算子:将原图像和拉普拉斯图像叠加到意思可以复原背景特性并保持拉普拉斯锐化处理的效果。拉普拉斯的定义是很重要的,如果所使用的定义具有负的中心系数,那么必须将原图像减去经过拉普拉斯变换后的图像而不是加上它,从而得到锐化结果。我们使用拉普拉斯对图像增强的基本方法可表示为如下公式:
- 非锐化掩蔽和高提升滤波:在印刷和出版界已经用了多年的图像锐化处理过程是从图像中减去一幅非锐化(平滑处理的图像)版本,这个称为非锐化掩蔽,具体处理过程步骤如下:A.模糊原图像;B.从原图像中减去模糊图像(产生的差值图像称为模板);C.将模板加到原图像上。
- 使用一阶微分对(非线性)图像锐化—梯度3.41图的d和e对应g_x, g_y。
-
混合空间增强法
-
使用模糊技术进行灰度变换和空间滤波:为了用模糊术语表示“平滑区”这一个概念,我们可以考虑邻域中心处像素和邻域像素间的灰度差。