原文:https://blog.csdn.net/guanzhen3657/article/details/81138868
常见的8种图像增强算法及其opencv实现
1.直方图均衡化
直方图均衡化是图像处理领域中利用图像直方图对对比度进行调整的方法。
这种方法通常用来增加许多图像的局部对比度。这种方法对于背景和前景都太亮或者太暗的图像非常有用,这种方法尤其是可以带来X光图像中更好的骨骼结构显示以及曝光过度或者曝光不足照片中更好的细节。这种方法的一个主要优势是它是一个相当直观的技术并且是可逆操作。
参考来源 openCV直方图均衡化https://blog.csdn.net/zhangfuliang123/article/details/74170894
首先openCV没有直方图直接显示的函数,所以我们需要创建直方图来自定义绘图,函数如下:
/*
width:直方图宽度 height:直方图高度 scale:
*/
IplImage* showImageHistogram(IplImage** image, int width, int height ,int scale){
int dims = 1;
int histSize = 256;
float frange[] = { 0, 255 };
float* ranges[] = { frange };
//创建一个直方图 CV_HIST_ARRAY多维密集数组
CvHistogram* hist = cvCreateHist(dims, &histSize, CV_HIST_ARRAY, ranges);
//根据输入图像计算直方图
cvCalcHist(image, hist);
//绘制直方图区域
IplImage* histImage = cvCreateImage(cvSize(width*scale, height), IPL_DEPTH_8U, 1);
//直方图背景区域置位白色
cvRectangle(histImage, cvPoint(0, 0), cvPoint(histImage->width, histImage->height), CV_RGB(255,255,255), CV_FILLED);
//获取最大值
float maxHistValue = 0;
cvGetMinMaxHistValue(hist, NULL, &maxHistValue, NULL,NULL);
//绘制各个灰度级的直方图
for (int i = 0; i < width; i++){
float value = cvQueryHistValue_1D(hist, i);
int drawHeight = cvRound((value / maxHistValue) * height);
cvRectangle(histImage, cvPoint(i*scale, height-1), cvPoint((i+1)*scale -1, height-drawHeight), cvScalar(i, 0, 0, 0), CV_FILLED);
}
return histImage;
}
直接处理灰度图:
- 使用OpenCV函数 EqualizeHist 对直方图均衡化
void histGrayChange(){
const char* picName = "test.tif";//test.tif lenaRGB.tif
//采用IplImage *img = cvLoadImage(picName)默认值是CV_LOAD_IMAGE_COLOR 读取无论原始图像的通道数是多少,都将被转换为3个通道读入。
//IplImage *img = cvLoadImage(picName);
//******以灰度图像读入,强制转换为单通道*****
IplImage *img = cvLoadImage(picName,CV_LOAD_IMAGE_GRAYSCALE);
if (img == NULL){
cout << "Load File Failed." << endl;
}
cout << "ChannelL:" << img->nChannels;
IplImage* imgDst = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
//直方图均衡化
cvEqualizeHist(img, imgDst);
cvNamedWindow("Origin", CV_WINDOW_AUTOSIZE);
cvShowImage("Origin", img);
cvNamedWindow("Result", CV_WINDOW_AUTOSIZE);
cvShowImage("Result", imgDst);
//
int histImageWidth = 255;
int histImageHeight = 150;
int histImageScale = 2;
IplImage *histImage1 = showImageHistogram(&img, histImageWidth, histImageHeight, histImageScale);
cvNamedWindow("Hist1", CV_WINDOW_AUTOSIZE);
cvShowImage("Hist1", histImage1);
IplImage *histImage2 = showImageHistogram(&imgDst, histImageWidth, histImageHeight, histImageScale);
cvNamedWindow("Hist2", CV_WINDOW_AUTOSIZE);
cvShowImage("Hist2", histImage2);
cvWaitKey();
cvDestroyWindow("Origin"); cvReleaseImage(&img);
cvDestroyWindow("Result"); cvReleaseImage(&imgDst);
}
也可以实现对彩色图片的直方图均衡化,代码就在前面的链接里,不贴了。
2、对数图像增强算法
对数图像增强是图像增强的一种常见方法,其公式为: S = c log(r+1),其中c是常数(以下算法c=255/(log(256)),这样可以实现整个画面的亮度增大。
void LogEnhance(IplImage* img, IplImage* dst)
{
// 由于oldPixel:[1,256],则可以先保存一个查找表
uchar lut[256] ={0};
double temp = 255/log(256);
for ( int i =0; i<255; i++)
{
lut[i] = (uchar)(temp* log(i+1)+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];
}
}
}
}
3、指数图像增强算法
指数图像增强的表达为:S = cR^r,通过合理的选择c和r可以压缩灰度范围,算法以c=1.0/255.0, r=2实现。
void 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];
}
}
}
}
4、加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);
}
}
}
}
}
5、曝光过度问题处理
对于曝光过度问题,可以通过计算当前图像的反相(255-image),然后取当前图像和反相图像的较小者为当前像素位置的值。
// 过度曝光原理:图像翻转,然后求原图与反图的最小值
void ExporeOver(IplImage* img, IplImage* dst)
{
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];
uchar t2 = 255 - t1;
dstData[col*img->nChannels+k] = min(t1,t2);
}
}
}
}
6、高反差保留
高反差保留主要是将图像中颜色、明暗反差较大两部分的交界处保留下来,比如图像中有一个人和一块石头,那么石头的轮廓线和人的轮廓线以及面部、服装等有明显线条的地方会变被保留,儿其他大面积无明显明暗变化的地方则生成中灰色。其表达形式为: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; }
测试代码:
int main(int argc, char* argv[]) { const char* Path = "02.bmp"; IplImage *img = cvLoadImage(Path,CV_LOAD_IMAGE_ANYCOLOR); IplImage *dst = cvCreateImage(cvGetSize(img), img->depth, img->nChannels); cout<<"输入你要选择的操作:"<<endl; cout<<"1、曝光过度"<<endl; cout<<"2、加马赛克"<<endl; cout<<"3、对数增强"<<endl; cout<<"4、指数增强"<<endl; cout<<"请输入你的选择:"; int choice = 1; cin>>choice; switch (choice) { case 1: ExporeOver(img, dst); //这四个算法中总觉得某个算法有问题 break; case 2: Masic(img, dst, 21); break; case 3: LogEnhance(img, dst); break; case 4: ExpEnhance(img, dst); break; default: cout<<"输入错误"<<endl; break; } cvSaveImage("dst.jpg",dst); cvNamedWindow("SRC",1); cvNamedWindow("DST", 1); cvShowImage("SRC", img); cvShowImage("DST", dst); cvWaitKey(); return 0; }
7.拉普拉斯算子图像增强
使用中心为5的8邻域拉普拉斯算子与图像卷积可以达到锐化增强图像的目的。
拉普拉斯算子增强局部的图像对比度的opencv代码:
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace cv;
int main(int argc, char *argv[])
{
Mat image = imread("/Users/shandiangou/Downloads/lena.png");
if (image.empty())
{
std::cout << "打开图片失败,请检查" << std::endl;
return -1;
}
imshow("原图像", image);
waitKey();
Mat imageEnhance;
Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, 0, 5, 0, 0, -1, 0);
filter2D(image, imageEnhance, CV_8UC3, kernel);
imshow("拉普拉斯算子图像增强效果", imageEnhance);
waitKey();
return 0;
}
参考博客:直方图均衡化、拉普拉斯算子图像增强、Gamma校正https://blog.csdn.net/sinat_28296297/article/details/77972023
OpenCV_基于Laplacian算子的图像边缘增强https://blog.csdn.net/icvpr/article/details/8502949
8.Gamma校正
伽马变换主要用于图像的校正,将灰度过高或者灰度过低的图片进行修正,增强对比度。伽马变换对图像的修正作用其实就是通过增强低灰度或高灰度的细节实现的。
实现代码
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
// Normalizes a given image into a value range between 0 and 255.
Mat norm_0_255(const Mat& src) {
// Create and return normalized image:
Mat dst;
switch(src.channels()) {
case 1:
cv::normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
break;
case 3:
cv::normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
break;
default:
src.copyTo(dst);
break;
}
return dst;
}
int main(int argc, const char *argv[]) {
// Get filename to the source image:
// Load image & get skin proportions:
Mat image = imread("/Users/shandiangou/Downloads/guobao.jpeg");
// Convert to floating point:
Mat X;
image.convertTo(X, CV_32FC1);
//image.convertTo(X, CV_32F);
// Start preprocessing:
Mat I;
float gamma = 3;
pow(X, gamma, I);
// Draw it on screen:
imshow("Original Image", image);
waitKey();
imshow("Gamma correction image", norm_0_255(I));
// Show the images:
waitKey();
// Success!
return 0;
}
暂时总结的就这8类,其他的之后再添