Image processing cheats: 12 image enhancement methods. [with code]

1. Contrast and brightness enhancement

Usage scenario: detection of bright spots (white points) on dark (black) backgrounds

For details, see:
contrast brightness image enhancement and convertTo detailed explanation

accomplish:

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);
}

2. Histogram equalization

Briefly, it is not available for the time being.

Histogram equalization maps an entire image and does not locally map some areas, and is not suitable for images that are dark or bright in some areas. At the same time, the gray level of the image after histogram equalization will be reduced, causing some details of the image to disappear.

It is more suitable for an overall dark or bright image. The gray value of the entire image can be evenly distributed within the entire dynamic range [0,255], thereby increasing the contrast of the image.

1. Custom cumulative frequency equalization method:

Steps:
1. Count the number of each gray value pixel in the image
2. Calculate the frequency of each gray value pixel, and calculate the cumulative frequency
3. Map the image, the gray value of the image = the original gray value of the image *Cumulative frequency

Single channel:

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;
}

Multi-channel (RGB):

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. The equalizeHist() that comes with opencv

equalizeHist(Mat src, Mat dst);

step:

1. Calculate the histogram H of the input image (8 bits);
2. Normalize the histogram H so that the sum of the histogram bins is 255;
3. Calculate the integral of the histogram (integral of the histogram); H′i =∑ 0≤j<i H(j)
4. Use H′ as a look-up table (look-uptable) to transform the image. The specific pixel value transformation formula is: dst(x,y)=H"(src(x ,y))

The algorithm normalizes the brightness and increases the contrast of the image.

Results will be different from custom ones.

3. Adaptive local histogram equalization

Local histogram processing:

(1) Set a template (rectangular neighborhood) of a certain size, and move pixel by pixel in the image; (
2) For each pixel position, calculate the histogram of the template area, and perform histogram equalization or histogram on the local area Image matching transformation, the transformation result is only used to correct the gray value of the pixel in the center of the template area;
(3) The template (neighborhood) moves row by row in the image, traversing all the pixels, and completes the local histogram of the entire image Figure processing.

C++ code:

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

python code:

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

clipLimit: color contrast threshold, optional, default value 8
titleGridSize: template (neighborhood) size for partial histogram equalization, optional, default value (8,8)

3. Exponential transformation enhancement

The formula of exponential transformation (Power-Law): S=c*R^r, the gray scale range can be compressed by choosing c and r reasonably, and the algorithm is realized with c=1.0/255.0, r=2.

insert image description here

Tested with a darker picture, the actual effect is darker. Consistent with this formulaic reasoning, the actual pixel value is replaced by the smaller value of the lookup table.

Custom exponential boosting algorithm:

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];
        }
    }
}

Note: Some blogs use expf(n) to calculate the nth power of e, which I think is wrong. expf(100) is already inf infinite.

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

Fourth, gamma enhancement

Special exponential enhancement, Gamma correction based on power transformation is a very important nonlinear transformation in image processing. It is the opposite of logarithmic transformation. It performs exponential transformation on the gray value of the input image, and then corrects the brightness difference. deviation.

Usually gamma correction is applied to expand the details of dark tones. Generally speaking, when the value of Gamma correction is greater than 1, the highlight part of the image is compressed and the shadow part is expanded; when the value of Gamma correction is less than 1, on the contrary, the highlight part of the image is expanded and the shadow backup is compressed .

output = L^γ
When γ is less than 1, the low grayscale range is stretched and the high grayscale range is compressed;
when γ is greater than 1, the low grayscale range is compressed and the high grayscale range is stretched.
When γ is equal to 1, it is simplified to identity transformation.

1. Fixed cubic enhancement:

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. Custom coefficient enhancement:

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;
}

Five, log conversion enhancement

efficient. After testing the blurred border enhancement of transparent objects, the effect was good.

Logarithmic transformation can expand low grayscale values ​​and compress high grayscale values, so that the grayscale distribution of the image is more in line with the visual characteristics of the human eye.

After proper processing, the contrast of the low-gray areas in the original image will be increased, and the details of the dark parts will be enhanced.

The logarithmic transformation formula is:

y = log(1+x)/b

Among them, b is a constant used to control the curvature of the curve, wherein, the smaller b is, the closer to the y-axis, and the larger b is, the closer to the x-axis. The x in the expression is the pixel value in the original image, and y is the transformed pixel value.

accomplish:

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);
 
}

Six, laplaceEhance enhancement

It has a sharpening effect and is sensitive to noise, so it needs to be smoothed first.

It is particularly effective for improving blur due to diffusion effects, since it fits the drop model. Diffusion effects are a phenomenon that often occurs during imaging.

The Laplacian operator is generally not used for edge detection in its original form, because as a second derivative, the Laplacian operator has unacceptable sensitivity to noise; at the same time, its magnitude produces an edge, which is not desirable for complex segmentation The result; finally the Laplacian operator cannot detect the direction of the edge.

accomplish:

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);
}

Seven, linear transformation:

There is nothing to say, just do the linear transformation directly.

y = kx+b;

accomplish:

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;
}

Eight, piecewise linear stretching algorithm:

Algorithms commonly used in image grayscale transformation also have corresponding functions in the commercial image editing software Photoshop. Segmented linear stretching is mainly used to improve image contrast and highlight image details.

insert image description here

insert image description here

Custom enhanced, weakened pixel intervals:

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];
		}
	}
}

Nine, gray level layering

The simplest example is the commonly used opencv binarization algorithm: thresh and inRange are both available.

Here you can make inferences by yourself.

10. Overexposure reverses the image

Literally: just do 255-x operation on the value.

accomplish:

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);
			}
		}		
	}
}

11. High contrast retention

High-contrast preservation is mainly to preserve the junction between the two parts of the image with a large contrast between light and dark. For example, if there is a person and a stone in the image, then the outline of the stone and the outline of the person, as well as the face, clothing, etc. have obvious lines. Places will be preserved, and other large areas without obvious changes in light and dark will generate medium gray.

Its expression form is:

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

accomplish:

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;
}

12. Masaic algorithm (mosaic)

In daily life, sometimes confidentiality or other needs to mosaic the image, the following algorithm realizes the image mosaic function (principle: use the central pixel to represent the neighboring pixels).

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);
                }
            }
        }
    }
}

13. Summary

The above methods are sometimes not used alone in actual combat, and the combination of filtering and various methods often achieves better results.

Guess you like

Origin blog.csdn.net/weixin_48846514/article/details/126924491