图像处理秘籍:12种图像增强方法。【附代码】

一、对比度与亮度增强

使用场景:深色(黑色)背景的亮点(白点)检测

详情见:
对比度亮度图像增强及convertTo详解

实现:

void adjust(const cv::Mat &src, cv::Mat &dst, const float brightness, const float contrast)
{
    
    
	double B = brightness / 255.;
   	double c = contrast / 255.;
	double k = tan((45 + 44 * c) / 180 * M_PI);
	double alpha = k;
	double beta = 127.5 * (1 + B) - k * 127.5 * (1 - B);
	src.convertTo(dst, -1, alpha, beta);
}

二、直方图均衡化

简单介绍一下,暂时用不到。

直方图均衡化是对一整幅图像进行映射,并不会对某些区域局部映射,对于那些部分区域偏暗或者偏亮的图像而言并不适用。同时直方图均衡化后的图像灰度级会减少,造成图像的一些细节消失。

对于一幅整体偏暗或者偏亮的图像而言比较适用。可以使得整幅图像的灰度值份均匀分布在整个动态范围[0,255]之内,从而增加图像的对比度。

1.自定义的累计频率均衡法:

步骤:
1.统计图像中每个灰度值像素的个数
2.计算每个灰度值像素的频率,并计算累计频率
3.将图像进行映射,图像的灰度值=图像原来灰度值*累计频率

单通道:

bool MyEqualizeHist(Mat gray, Mat & result) 
{
    
    

     //统计0~255像素值的个数
     map<int, int>mp;
     for (int i = 0; i < gray.rows; i++)
	 {
    
    
           uchar* ptr = gray.data + i * gray.cols;
           for (int j = 0; j < gray.cols; j++)
		   {
    
    
               int value = ptr[j];
               mp[value]++;
           }
      }

      //统计0~255像素值的频率,并计算累计频率
      map<int, double> valuePro;
      int sum = gray.cols*gray.rows;
      double  sumPro = 0;
      for (int i = 0; i < 256; i++) 
	  {
    
    
          sumPro += 1.0*mp[i] / sum;
          valuePro[i] = sumPro;
      }
      //根据累计频率进行转换
      for (int i = 0; i < gray.rows; i++) 
	  {
    
    
          uchar* ptr1 = gray.data + i*gray.cols;
          for (int j = 0; j < gray.cols; j++) {
    
    
             int value = ptr1[j];
             double p = valuePro[value];
             result.at<uchar>(i, j) = p*value;
          }
       }
       return true;
}

多通道(RGB):

扫描二维码关注公众号,回复: 15361327 查看本文章
void MyEqualizeHistRgb(Mat& image)
{
    
    
	Mat imageRGB[3];
	split(image, imageRGB);
	for (int i = 0; i < 3; i++)
	{
    
    
		MyEqualizeHist(imageRGB[i], imageRGB[i]);
	}
	merge(imageRGB, 3, image);

	imshow("result", image);
}

2.opencv自带的equalizeHist()

equalizeHist(Mat src, Mat dst);

步骤:

1.计算输入图像(8位)的直方图H;
2. 对直方图H进行归一化,使直方图bins的总和为255;
3.计算直方图的积分(integral of thehistogram); H′i=∑ 0≤j<i H(j)
4.使用H′作为查找表( look-uptable)对图像进行变换,具体像素值的变换公式为:dst(x,y)=H"(src(x,y))

该算法归一化亮度并增加图像的对比度。

结果与自定义的会不同。

3.自适应的局部直方图均衡化

局部直方图处理过程:

(1)设定某一大小的模板(矩形邻域),在图像中沿逐个像素移动;
(2)对每个像素位置,计算模板区域的直方图,对该局部区域进行直方图均衡或直方图匹配变换,变换结果只用于模板区域中心像素点的灰度值修正;
(3)模板(邻域)在图像中逐行逐列移动,遍历所有像素点,完成对整幅图像的局部直方图处理。

C++代码:

//C++
cvtColor(img,gray,COLOR_BGR2GRAY);
Ptr<CLAHE> clahe = createCLAHE();
clahe->apply(gray, dst);

python代码:

//python
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(4,4))  # 创建 CLAHE 对象
imgLocalEqu = clahe.apply(img)  # 自适应的局部直方图均衡化

clipLimit:颜色对比度的阈值,可选项,默认值 8
titleGridSize:局部直方图均衡化的模板(邻域)大小,可选项,默认值 (8,8)

三、指数变换增强

指数变换(Power-Law )的公式:S=c*R^r, 通过合理的选择c和r可以压缩灰度范围,算法以c=1.0/255.0, r=2实现。

在这里插入图片描述

以偏暗的图片测试,实际效果更暗。符合该公式推理,实际的像素值替换成了查找表的较小值。

自定义指数增强算法:

void Enhance::ExpEnhance(IplImage* img, IplImage* dst)
{
    
    
    // 由于oldPixel:[1,256],则可以先保存一个查找表
    uchar lut[256] ={
    
    0};
 
    double temp = 1.0/255.0;
 
    for ( int i =0; i<255; i++)
    {
    
    
        lut[i] = (uchar)(temp*i*i+0.5);
    }
 
    for( int row =0; row <img->height; row++)
    {
    
    
        uchar *data = (uchar*)img->imageData+ row* img->widthStep;
        uchar *dstData = (uchar*)dst->imageData+ row* dst->widthStep;
 
        for ( int col = 0; col<img->width; col++)
        {
    
    
            for( int k=0; k<img->nChannels; k++)
            {
    
    
                uchar t1 = data[col*img->nChannels+k];                
                dstData[col*img->nChannels+k] = lut[t1];
            }
        }        
    }    
}

//  OpenCV2.1版本之前使用IplImage*数据结构来表示图像,2.1之后的版本使用图像容器Mat来存储。
//	Mat dstImage = cvarrToMat(&dst1); // 将IplImage格式转换为Mat格式 
// 	IplImage dst1= (IplImage)(dst);// 将Mat类型的图片转换为IplImage 

// 以下为原创使用Mat的版本。

void Enhance::ExpEnhance(Mat & img,double c, int r)
{
    
    
    if(img.channels() != 1)
    {
    
    
        cvtColor(img,img,COLOR_BGR2GRAY);
    }

    uchar lut[256] ={
    
    0};
    for ( int i =0; i<255; i++)
    {
    
    
        lut[i] = (uchar)( c * pow(i,r) +0.5);
        cout << c*i*i+0.5<<endl;
    }

    for(int i = 0;i < img.rows; i++)
    {
    
    
        for(int j = 0;j < img.cols; j++)
        {
    
    
            int pv = img.at<uchar>(i,j);
            img.at<uchar>(i,j) = lut[pv];
        }
    }
}

注:部分博客使用expf(n)计算e的n次方,个人认为是错的。expf(100)已经inf无穷大了。

srcmat.at<uchar>(i, j) = (unsigned char)(255.0 *(expf(srcmat.at<uchar>(i, j) - low_bound) / expf(up_bound - low_bound))); 

四、gamma增强

特殊的指数增强,基于幂次变换的Gamma校正是图像处理中一种非常重要的非线性变换,它与对数变换相反,它是对输入图像的灰度值进行指数变换,进而校正亮度上的偏差。

通常Gamma校正长应用于拓展暗调的细节。通常来讲,当Gamma校正的值大于1时,图像的高光部分被压缩而暗调部分被扩展;当Gamma校正的值小于1时,相反的,图像的高光部分被扩展而暗调备份被压缩。

output = L^γ
当γ小于1时,低灰度区间拉伸,高灰度区间压缩;
当γ大于1时,低灰度区间压缩,高灰度区间拉伸。
当γ等于1时,简化成恒等变换。

1.固定三次方增强:

void Enhance::gammaEhance(Mat& image)
{
    
    
	Mat imageGamma(image.size(), CV_32FC3);
	for (int i = 0; i < image.rows; i++)
	{
    
    
		for (int j = 0; j < image.cols; j++)
		{
    
    
			imageGamma.at<Vec3f>(i, j)[0] = (image.at<Vec3b>(i, j)[0])*(image.at<Vec3b>(i, j)[0])*(image.at<Vec3b>(i, j)[0]);
			imageGamma.at<Vec3f>(i, j)[1] = (image.at<Vec3b>(i, j)[1])*(image.at<Vec3b>(i, j)[1])*(image.at<Vec3b>(i, j)[1]);
			imageGamma.at<Vec3f>(i, j)[2] = (image.at<Vec3b>(i, j)[2])*(image.at<Vec3b>(i, j)[2])*(image.at<Vec3b>(i, j)[2]);
		}
	}
	//归一化到0~255  
	normalize(imageGamma, imageGamma, 0, 255, CV_MINMAX);
	//转换成8bit图像显示  
	convertScaleAbs(imageGamma, imageGamma);
	imshow("gamma三次方增强", imageGamma);
}

2.自定义系数增强:

Mat Enhance::gammaWithParameter(Mat &img, float parameter)
{
    
    
	//建立查表文件LUT
	unsigned char LUT[256];
	for (int i = 0; i < 256; i++)
	{
    
    
		//Gamma变换定义
		LUT[i] = saturate_cast<uchar>(pow((float)(i / 255.0), parameter)*255.0f);
	}
	Mat dstImage = img.clone();
	//输入图像为单通道时,直接进行Gamma变换
	if (img.channels() == 1)
	{
    
    
		MatIterator_<uchar>iterator = dstImage.begin<uchar>();
		MatIterator_<uchar>iteratorEnd = dstImage.end<uchar>();
		for (; iterator != iteratorEnd; iterator++)
			*iterator = LUT[(*iterator)];
	}
	else
	{
    
    
		//输入通道为3通道时,需要对每个通道分别进行变换
		MatIterator_<Vec3b>iterator = dstImage.begin<Vec3b>();
		MatIterator_<Vec3b>iteratorEnd = dstImage.end<Vec3b>();
		//通过查表进行转换
		for (; iterator!=iteratorEnd; iterator++)
		{
    
    
			(*iterator)[0] = LUT[((*iterator)[0])];
			(*iterator)[1] = LUT[((*iterator)[1])];
			(*iterator)[2] = LUT[((*iterator)[2])];
		}
	}
	return dstImage;
}

五、log转换增强

有效。测试透明物品模糊的边界增强后获得不错效果。

对数变换可以拓展低灰度值而压缩高灰度级值,让图像的灰度分布更加符合人眼的视觉特征。

经过适当的处理后,原始图像中低灰度区域的对比度将会增加,暗部细节将被增强。

对数变换公式为:

y = log(1+x)/b

其中,b是一个常数,用来控制曲线的弯曲程度,其中,b越小越靠近y轴,b越大越靠近x轴。表达式中的x是原始图像中的像素值,y是变换后的像素值。

实现:

void logEhance(Mat& image)
{
    
    
	Mat imageLog(image.size(), CV_32FC3);
 
	for (int i = 0; i < image.rows; i++)
	{
    
    
		for (int j = 0; j < image.cols; j++)
		{
    
    
			imageLog.at<Vec3f>(i, j)[0] = log(1 + image.at<Vec3b>(i, j)[0]);
			imageLog.at<Vec3f>(i, j)[1] = log(1 + image.at<Vec3b>(i, j)[1]);
			imageLog.at<Vec3f>(i, j)[2] = log(1 + image.at<Vec3b>(i, j)[2]);
		}
	}
	//归一化到0~255  
	normalize(imageLog, imageLog, 0, 255, CV_MINMAX);
	//转换成8bit图像显示  
	convertScaleAbs(imageLog, image);
	//imshow("Soure", image);
	imshow("Log", image);
 
}

六、laplaceEhance增强

拥有锐化作用,对噪声比较敏感,需要先做平滑处理。

用来改善因扩散效应的模糊特别有效,因为它符合降制模型。扩散效应是成像过程中经常发生的现象。

Laplacian算子一般不以其原始形式用于边缘检测,因为其作为一个二阶导数,Laplacian算子对噪声具有无法接受的敏感性;同时其幅值产生算边缘,这是复杂的分割不希望有的结果;最后Laplacian算子不能检测边缘的方向.

实现:

void Enhance::laplaceEhance(Mat& image)
{
    
    
	Mat imageEnhance;
	Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, 0, 5, 0, 0, -1, 0);

    //多种卷积核可选。

    //Mat kernel = (Mat_<float>(3, 3) << 0, 1, 0, 1, -4, 1, 0, 1, 0);
    //Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
    //Mat kernel = (Mat_<float>(3, 3) << 0, 1, 0, 1, 3, 1, 0, 1, 0);
    //Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);

	filter2D(image, imageEnhance, CV_8UC3, kernel);
	imshow("laplaceEhance", imageEnhance);
}

七、线性变换:

没什么好说的,就是直接做线性变换。

y = kx+b;

实现:

Mat Enhance::linearTransformation(Mat img, const float k, const float b)
{
    
    
    Mat dst = img.clone();
    for(int i=0; i<img.rows; i++)
    {
    
    
        for(int j=0; j<img.cols; j++)
        {
    
    
            for(int c=0; c<3; c++)
            {
    
    
				float x =img.at<Vec3b>(i, j)[c];
				dst.at<Vec3b>(i, j)[c] = saturate_cast<uchar>(k* x + b);            }
        }
    }
	return dst;
}

八、分段线性拉伸算法:

图像灰度变换中常用的算法,在商业图像编辑软件Photoshop中也有相应的功能。分段线性拉伸主要是用于提高图像对比度,突显图像细节。

在这里插入图片描述

在这里插入图片描述

自定义增强,减弱的像素区间:

void Enhance::PiecewiseLinearTrans(cv::Mat& matInput, float x1, float x2, float y1, float y2)
{
    
    
	//计算直线参数
	//L1
	float K1 = y1 / x1;
	//L2
	float K2 = (y2 - y1) / (x2 - x1);
	float C2 = y1 - K2 * x1;
	//L3
	float K3 = (255.0f - y2) / (255.0f - x2);
	float C3 = 255.0f - K3 * 255.0f;

	//建立查询表
    uchar LUT[256] ={
    
    0};
	for (int m = 0; m < 256; m++)
	{
    
    
		if (m < x1)
		{
    
    
			LUT[m] = m * K1;
		}
		else if (m > x2)
		{
    
    
			LUT[m] = m * K3 + C3;
		}
		else
		{
    
    
			LUT[m] = m * K2 + C2;
		}
	}
	//灰度映射
	for (int j = 0; j < matInput.rows; j++)
	{
    
    
		for (int  i = 0; i < matInput.cols; i++)
		{
    
    
			//查表gamma变换
            int x = matInput.at<uchar>(j,i);
            matInput.at<uchar>(j,i) = LUT[x];
		}
	}
}

九、灰度级分层

最简单的例子就是常用的opencv二值化算法:thresh、inRange都可以。

此处可以自行举一反三。

十、曝光过度对图像取反

字面意思:对值做255-x的操作即可。

实现:

void ExporeOver(IplImage* img, IplImage* dst)
{
    
    
	for( int row =0; row height; row++)
	{
    
    
		uchar *data = (uchar*)img->imageData+ row* img->widthStep;
		uchar *dstData = (uchar*)dst->imageData+ row* dst->widthStep;
		for ( int col = 0; colwidth; col++)
		{
    
    
			for( int k=0; knChannels; k++)
			{
    
    
				uchar t1 = data[col*img->nChannels+k];
				uchar t2 = 255 - t1;
				dstData[col*img->nChannels+k] = min(t1,t2);
			}
		}		
	}
}

十一、高反差保留

高反差保留主要是将图像中颜色、明暗反差较大两部分的交界处保留下来,比如图像中有一个人和一块石头,那么石头的轮廓线和人的轮廓线以及面部、服装等有明显线条的地方会变被保留,儿其他大面积无明显明暗变化的地方则生成中灰色。

其表达形式为:

dst = r*(img – Blur(img))。

实现:

Mat HighPass(Mat img)
{
    
    
	Mat temp;
	GaussianBlur(img, temp,Size(7,7),1.6,1.6);
 
	int r=3;	
	Mat diff = img + r*(img-temp); //高反差保留算法
	return diff;
}

十二、Masaic算法(马赛克)

在日常中有时候保密或其他需要将图像马赛克,下面的算法实现图像马赛克功能(原理:用中心像素来表示邻域像素)。

uchar getPixel( IplImage* img, int row, int col, int k)
{
    
    
    return ((uchar*)img->imageData + row* img->widthStep)[col*img->nChannels +k];
}
 
void setPixel( IplImage* img, int row, int col, int k, uchar val)
{
    
    
    ((uchar*)img->imageData + row* img->widthStep)[col*img->nChannels +k] = val;
}

// nSize:为尺寸大小,奇数
// 将邻域的值用中心像素的值替换
void Masic(IplImage* img, IplImage* dst, int nSize)
{
    
    
    int offset = (nSize-1)/2;
    for ( int row = offset; row <img->height - offset; row= row+offset)
    {
    
    
        for( int col= offset; col<img->width - offset; col = col+offset)
        {
    
    
            int val0 = getPixel(img, row, col, 0);
            int val1 = getPixel(img, row, col, 1);
            int val2 = getPixel(img, row, col, 2);
            for ( int m= -offset; m<offset; m++)
            {
    
    
                for ( int n=-offset; n<offset; n++)
                {
    
    
                    setPixel(dst, row+m, col+n, 0, val0);
                    setPixel(dst, row+m, col+n, 1, val1);
                    setPixel(dst, row+m, col+n, 2, val2);
                }
            }
        }
    }
}

十三、总结

以上众多的方法在实战中有时不是单独使用的,滤波与各个方法相互的结合往往取得更好的效果。

猜你喜欢

转载自blog.csdn.net/weixin_48846514/article/details/126924491
今日推荐