目的
访问像素值mat.At<T>(y,x)
用0初始化矩阵Mat.Zeros
饱和操作SaturateCast.ToByte
亮度和对比度调整
g(x)=αf(x)+β
用α(>0)和β一般称作增益(gain)和偏置(bias),分别控制对比度和亮度
把f(x)看成源图像像素,把g(x)看成输出图像像素
g(i,j)=α⋅f(i,j)+β
其中,i和j表示像素位于 第i行 和 第j列(左上角为第0行、第0列)
相关函数
Mat.Zeros:初始一个所有值为0的矩阵
Mat new_Image1 = Mat.Zeros(image.Size(), image.Type());
SaturateCast.ToByte:饱和操作,当<0时,返回0,当>255时,返回255,其它返回原值
return SaturateCast.ToByte(alpha * source + beta);
Mat.ConvertTo: g(i,j)=α⋅f(i,j)+β 转化
image.ConvertTo(new_Image2, image.Type(), alpha, beta);
SaturateCast.ToByte与ConvertTo的差异
使用SaturateCast.ToByte与ConvertTo操作的结果不完全一致。
如计算85*0.7+0.0=59.499999999999993D, SaturateCast.ToByte的结果为59,而ConvertTo的结果为60。
图像示例
当 α=2.2 and β=80时的效果图
α、β参数的意义
官网源图
β越大,图像越亮,直方图往右
使用前面的公式g(i,j)=α⋅f(i,j)+β对图像进行亮度调整,当α=1,β=80时
对比原图(左)与调整后(右)的直方图,调整亮度后,直方图整体往右压缩。因为饱和操作的结果是,大于255的值设为255(为对比显示效果,255的个数没有显示完全)。(直方图:往左变暗,往右变亮)
α<1时,对比度变小
当β相同80,左边α=1,右边α=0.5,对比可知,当α<1时,图像色阶变小,对比度变小。
(左边α=1,右边α=0.5)
结论:调大β,可以变亮,因为对比度的变小导致有轻微蒙纱。调小α,可有效改善,但会损失原来明亮部分的细节。
伽马校正
伽马校正可用于亮度的非性调整。公式如下:
对比不同的像素的输入与输出值(图片来源于OpenCV官网)
下图为原图与α=1.3,β=40时的对比
结论:图像是调亮了,但天空的细节少了
下图为:原图与gamma=0.4时的对比
图像调亮了,细节也在。
下图为,原图(左)、α=1.3,β=40调整(中)及gamma=0.4(右)调整的直方图对比
对起简单的线性对比度和亮度调整,伽马校正效果更优。
源码示例
Mat src;//源图
string winName = "Brightness and contrast adjustments";
int alpha = 100;
int beta = 255;
string gammaWinName = "Gamma correction";
Mat saturateCastHistImg;//SaturateCast后的图像直方图
double maxVal = -1;//源图最多像素点个数
public void Run(ParamBase paramBase) {
//src = Cv2.ImRead(ImagePath.Lena);
src = Cv2.ImRead(ImagePath.Underexposed);
if (src.Empty()) throw new Exception("图像打开有误");
//原图像的直方图
GetHistResult(src, out _, out Mat histImg);
Cv2.ImShow("Hist:Original", histImg);
Cv2.NamedWindow(winName, WindowFlags.AutoSize);
Cv2.CreateTrackbar("α=n/100", winName, ref alpha, 500, alphaOnChange);
Cv2.CreateTrackbar("β=n-255", winName, ref beta, 500, betaOnChange);
//Cv2.ImShow("Original Image", image);
Cv2.NamedWindow(gammaWinName, WindowFlags.AutoSize);
Cv2.CreateTrackbar("γ= n/100", gammaWinName, 200, gammaOnChange);
Cv2.SetTrackbarPos("γ= n/100", gammaWinName, 100);
Cv2.WaitKey();
Cv2.DestroyAllWindows();
}
#region 滚动条Bug,多次滚动后,ref value的值与pos不同步
private void alphaOnChange(int pos, IntPtr userdata) {
alpha = pos;
OnChange();
}
private void betaOnChange(int pos, IntPtr userdata) {
beta = pos;
OnChange();
}
#endregion
private void OnChange() {
//对比度
double alphaD = alpha / 100.0D;
//亮度
double betaD = beta - 255;
//初始化所有值都为0的矩阵
Mat new_Image1 = Mat.Zeros(src.Size(), src.Type());
//使用公式:g(i,j)=α⋅f(i,j)+β
//方式一
for (int row = 0; row < src.Rows; row++) {
for (int col = 0; col < src.Cols; col++) {
var val = src.At<Vec3b>(row, col);//访问图像像素
Vec3b newVal = new Vec3b();
for (int c = 0; c < src.Channels(); c++) {
newVal[c] = Adjust(val[c], alphaD, betaD);
}
new_Image1.At<Vec3b>(row, col) = newVal;
}
}
//方式二
Mat new_Image2 = Mat.Zeros(src.Size(), src.Type());
src.ConvertTo(new_Image2, src.Type(), alphaD, betaD);
//比较 SaturateCast.ToByte与 ConvertTo的结果是否一样
using var difMat = new Mat();
Cv2.Absdiff(new_Image1, new_Image2, difMat);
if (difMat.Split().Any(z => z.CountNonZero() > 0)) {
//有差异
PutText(new_Image2, "Different", new Point(5, 30));
//注意 85*0.7 = 59.499999999999993 double
}
GetHistResult(new_Image1, out _, out saturateCastHistImg);
var adjust = $"alpha={alphaD.ToString("0.00")},beta={betaD}";
PutText(saturateCastHistImg, adjust);
Cv2.ImShow("Hist,SaturateCast", saturateCastHistImg);
PutText(new_Image1, adjust);
Cv2.HConcat(src, new_Image1, new_Image1);
PutText(new_Image2, $"alpha={alphaD.ToString("0.00")},beta={betaD}");
Cv2.HConcat(src, new_Image2, new_Image2);
Cv2.ImShow(winName, new_Image1);
Cv2.ImShow("ConvertTo", new_Image2);
}
/// <summary>
/// 调整对比度和亮度
/// </summary>
/// <param name="source">源值</param>
/// <param name="alpha">对比度</param>
/// <param name="beta">亮度</param>
/// <returns></returns>
private byte Adjust(byte source, double alpha, double beta) {
//饱和操作,当<0时,返回0,当>255时,返回255,其它返回原值
return SaturateCast.ToByte(alpha * source + beta);
}
#region 直方图相关
/// <summary>
/// 计算并生成绘制直方图
/// </summary>
/// <param name="src">待统计的图像</param>
/// <param name="hist">直方图结果</param>
/// <param name="histImage">直方图的绘制结果</param>
private void GetHistResult(Mat src, out Mat hist, out Mat histImage) {
hist = new Mat();
const int histW = 512;
const int histH = 400;
histImage = new Mat(histH, histW, MatType.CV_8UC3, Scalar.All(0));
int histSize = 256;//直方图数组大小
var range = new Rangef(0, 256);//统计0至255(=266-1)
//将图像像素灰度[0,255]共分为histSize个等级统计,
for (int channel = 0; channel < src.Channels(); channel++) {
Cv2.CalcHist(images: new[] { src },//待统计的图像
channels: new[] { channel },//待统计的通道
mask: null,//掩膜
hist: hist,//输出的统计结果
dims: 1,//直方图维度
histSize: new[] { histSize },//将range分为histSize梯度
ranges: new[] { range });//待统计通道像素的范围,不在这个范围内的不统计
DrawHist(histImage, hist, (channel == 0 ? Scalar.Blue : (channel == 1 ? Scalar.Green : Scalar.Red)));
}
}
/// <summary>
/// 绘制直方图
/// </summary>
/// <param name="histImage">直方图绘制结果</param>
/// <param name="histSize">直方图数组大小</param>
/// <param name="color">线的颜色</param>
private void DrawHist(Mat histImage, Mat hist, Scalar color) {
var binW = Math.Round((double)histImage.Width / hist.Height);
if (maxVal > 0) {
//截断超过源图最大值的像素大数(防止饱和像素过多)
Cv2.Threshold(hist, hist, maxVal, maxVal, ThresholdTypes.Trunc);
}
//归一化
Cv2.Normalize(hist, hist, 0, histImage.Rows, NormTypes.MinMax, -1);
for (int i = 1; i < hist.Height; i++) {
var pt1 = new Point2d(binW * (i - 1), histImage.Height - Math.Round(hist.At<float>(i - 1)));
var pt2 = new Point2d(binW * (i), histImage.Height - Math.Round(hist.At<float>(i)));
//OpenCvSharp有Bug?有时会报错
Cv2.Line(histImage, (Point)pt1, (Point)pt2, color, 1, LineTypes.AntiAlias);
}
}
#endregion
#region Gamma矫正
private Mat GammaCorrection(Mat src,double gamma) {
var lookUpTable = new Mat(new Size(1, 256), MatType.CV_8U);
for (int i = 0; i < 256; i++) {
lookUpTable.At<byte>(0, i) = SaturateCast.ToByte(Math.Pow(i / 255.0D, gamma) * 255.0D);
}
Mat dst = new Mat();
//查表法,性能优化
Cv2.LUT(src, lookUpTable, dst);
return dst;
}
private void gammaOnChange(int pos,IntPtr userdata) {
double gamma = pos / 100.0D;
using var dst = GammaCorrection(src, gamma);
GetHistResult(dst, out _, out Mat histImg);
var adjust = $"gamma={gamma.ToString("0.00")}";
PutText(histImg,adjust);
Cv2.ImShow("Hist,Gamma Correction", histImg);
PutText(dst, adjust);
//左右合并
Cv2.HConcat(src, dst, dst);
Cv2.ImShow(gammaWinName, dst);
}
#endregion
private void PutText(Mat src, string text) {
PutText(src, text, new Point(5, 15));
}
private void PutText(Mat src, string text, Point point) {
Cv2.PutText(src, text, point, HersheyFonts.HersheySimplex, 0.5, Scalar.Red);
}
参考文章来源:https://www.toymoban.com/news/detail-774439.html
https://docs.opencv.org/4.7.0/d3/dc1/tutorial_basic_linear_transform.html文章来源地址https://www.toymoban.com/news/detail-774439.html
到了这里,关于OpenCvSharp学习笔记6--改变图像的对比度和亮度的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!