直方图规定化
1 原理
直方图均衡可以使图像的灰度分布产生均分分布的特性,是一种较为方便的图像增强的方法。但在某些应用中,尤其是希望输出图像的直方图具备特定的直方图形状时,直方图均衡的效果就欠佳了。此时需要用到直方图匹配或直方图规定化。
先在连续空间中讨论,将连续随机变量 r r r和 z z z分别看成输入图像和输出图像的灰度,那么 p r ( r ) p_{r}(r) pr(r)和 p z ( z ) p_{z}(z) pz(z)分别表示对应的概率密度函数,即 p r ( r ) p_{r}(r) pr(r)表示输入图像的概率密度函数, p z ( z ) p_{z}(z) pz(z)表示希望输出图像具有的概率密度函数。
令
s
s
s为一个具有以下特性的随机变量:
s
=
T
(
r
)
=
(
L
−
1
)
∫
0
r
p
r
(
w
)
d
w
s=T(r)=(L-1)\int_{0}^{r}{p_{r}(w)dw}
s=T(r)=(L−1)∫0rpr(w)dw
其中
w
w
w为积分假变量。
定义随机变量
z
z
z具有如下的特性:
G
(
z
)
=
(
L
−
1
)
∫
0
z
p
z
(
t
)
d
t
=
s
G(z)=(L-1)\int_{0}^{z}p_{z}(t)dt=s
G(z)=(L−1)∫0zpz(t)dt=s
其中
t
t
t为积分假变量。由此可以得出
G
(
z
)
=
T
(
r
)
G(z)=T(r)
G(z)=T(r),因此,
z
z
z必须满足下列条件:
z
=
G
−
1
[
T
(
r
)
]
=
G
−
1
(
s
)
z=G^{-1}[T(r)]=G^{-1}(s)
z=G−1[T(r)]=G−1(s)
由以上的推理过程,可以得出直方图规定化的执行过程,对一幅给定图像得到一幅其灰度级具有指定概率密度函数的图像过程如下:
- 1 由输入图像得到 p r ( r ) p_{r}(r) pr(r),并求得 s s s的值;
- 2 使用上式中指定的PDF求 G ( z ) G(z) G(z);
- 3 求的反变换函数 z = G − 1 ( s ) z = G^{-1}(s) z=G−1(s);因为 z z z是由 s s s得到的,因此,该处理是 s s s到 z z z的映射,而后者正是我们期望的值。
- 4 首先,对输入图像进行直方图均衡得到数数图像,该图像的像素值时 s s s值,对均衡后图像中具有 s s s值得每个像素进行反映射 z = G − 1 ( s ) z = G^{-1}(s) z=G−1(s),得到输出图像中的相应像素当所有的像素都处理完成之后,输出图像的PDF等于指定的PDF。
示例:
对一幅大小为64×64像素(MN=4096)的3比特图像(L=8)的灰度分布如下表所示:
r k r_k rk | n k n_k nk | p r ( r k ) = n k M N p_{r}(r_k)=\frac{n_k}{MN} pr(rk)=MNnk |
---|---|---|
r 0 = 0 r_{0}=0 r0=0 | 790 | 0.19 |
r 1 = 1 r_{1}=1 r1=1 | 1023 | 0.25 |
r 2 = 2 r_{2}=2 r2=2 | 850 | 0.21 |
r 3 = 3 r_{3}=3 r3=3 | 656 | 0.16 |
r 4 = 4 r_{4}=4 r4=4 | 329 | 0.08 |
r 5 = 5 r_{5}=5 r5=5 | 245 | 0.06 |
r 6 = 6 r_{6}=6 r6=6 | 122 | 0.03 |
r 7 = 7 r_{7}=7 r7=7 | 81 | 0.02 |
确定映射关系执行直方图规定化的过程如下:
在实际开发过程中,可以按如下思路执行:
- 1 计算输入图像的灰度直方图,并求得该直方图对应的累积概率分布图;
- 2 计算规定目标图像的灰度直方图,并求得该直方图对应的累积概率分布图;
- 3 将输入图像的累积概率分布图中的各个值,在规定目标图像的累积概率分布图上查找与之最接近的值,并将该值对应的灰度级与输入图像的各个值行成查找表。
- 4 对输入图像执行查表操作。
个人认为直方图规定化可以这么理解:输入图A具有直方图ZA,输入图B具有直方图ZB,为了让图A具有类似ZB的直方图,选择使用直方图均衡进行过渡,对A进行直方图均衡得到结果JA,JA具有1(L-1)的分布,对B进行直方图均衡得到结果JB,JB具有1(L-1)的分布,此时,将JA的像素与JB的像素一一对应,就能得到结果图像。
2 Matlab实现
2.1 Matlab函数实现
Matlab的直方图规定化的函数还是histeq
,该函数使用如下调用方式时将执行直方图规定化过程:
% 读取源图像
srcimg = imread('../images/18.jpg');
srcimg = rgb2gray(srcimg); % 源图像灰度化
[srcHist,srcX] = imhist(srcimg,256);% 计算源图像的直方图
dstimg = imread('../images/3.jpg'); % 读取直方图规定化的目标图像
dstimg = rgb2gray(dstimg);% 目标图像灰度化
[dstHist,dstX] = imhist(dstimg,256); % 计算目标图像的灰度直方图
g2 = histeq(srcimg,dstHist);
[g2Hist,g2X] = imhist(g2);
2.2 自己造轮子
clc;
clear;
close all;
% 读取源图像
srcimg = imread('../images/18.jpg');
srcimg = rgb2gray(srcimg); % 源图像灰度化
[srcHist,srcX] = imhist(srcimg,256);% 计算源图像的直方图
srcHist = srcHist/numel(srcimg);% 直方图归一化
cps = zeros(256,1,'double');% 计算源图像的灰度累积概率
for i=1:1:256
cps(i) = sum(srcHist(1:i));
end
cps = 255*cps; % 构建源图像灰度均衡化的变换函数
cps = uint8(cps);
dstimg = imread('../images/3.jpg'); % 读取直方图规定化的目标图像
dstimg = rgb2gray(dstimg);% 目标图像灰度化
[dstHist,dstX] = imhist(dstimg,256); % 计算目标图像的灰度直方图
dstHist = dstHist/sum(dstHist); % 灰度直方图归一化
cpd = zeros(256,1,'double');% 计算目标图像的灰度分布累积概率
for i=1:1:256
cpd(i) = sum(dstHist(1:i));
end
cpd = cpd*255; % 构建目标图像的灰度均衡化变换函数
cpd = uint8(cpd);
srcl=zeros(256,1,'uint8');
minv = 256;
for i = 1:1:256
minv =256;
for j = 1:1:256
if minv > abs(cps(i)-cpd(j))
minv = abs(cps(i)-cpd(j));
srcl(i) =j;
end
end
end
[width,height] = size( srcimg);
gray1 = srcimg;
for i=1:1:width
for j = 1:1:height
gray1(i,j)=srcl(srcimg(i,j)+1);
end
end
[g1Hist,g1X] = imhist(gray1);
% g1Hist = g1Hist/sum(g1Hist);
g2 = histeq(srcimg,dstHist);
[g2Hist,g2X] = imhist(g2);
% g2Hist = g2Hist/sum(g2Hist);
figure(1),
subplot(2,4,1),imshow(srcimg),title('原图');
subplot(2,4,2),imshow(dstimg),title('匹配图');
subplot(2,4,3),imshow(gray1),title('结果1');
subplot(2,4,4),imshow(g2),title('结果2');
subplot(2,4,5),stem(srcX,srcHist),title('原图直方图');
subplot(2,4,6),stem(dstX,dstHist),title('匹配图直方图');
subplot(2,4,7),stem(g1X,g1Hist),title('结果1直方图');
subplot(2,4,8),stem(g2X,g2Hist),title('结果2直方图');
3 C++实现
自己造轮子的快乐。。。。
3.1 自己造轮子
void CalcNormalizedHist1D(cv::Mat& img, cv::Mat& hist)
{
int channels[] = { 0 };
int histsize[] = { 256 };
float grayRnage[] = { 0,256 };
const float* ranges[] = { grayRnage };
cv::calcHist(&img, 1, channels, cv::Mat(), hist, 1, histsize, ranges);
hist = hist*1.0 / (img.rows*img.cols);
}
void CalcCSum(cv::Mat& normalizedHist, cv::Mat& matCSum)
{
matCSum = cv::Mat(normalizedHist.rows, normalizedHist.cols, CV_32FC1);
for (int i = 0; i < normalizedHist.rows; i++)
{
if (i == 0)
{
matCSum.at<float>(i, 0) = normalizedHist.at<float>(i, 0);
}
else
{
matCSum.at<float>(i, 0) = matCSum.at<float>(i-1, 0)+normalizedHist.at<float>(i, 0);
}
}
}
void CreateHSLT(cv::Mat& CSumSrc, cv::Mat& CSumDst, cv::Mat& lut)
{
lut = cv::Mat(1, 256, CV_8UC1);
float fMinV = 2;
int nMinLoc = 0;
for (int i = 0; i < CSumSrc.rows; i++)
{
fMinV = 2;
float f1 = CSumSrc.at<float>(i, 0);
for (int j = nMinLoc; j < CSumDst.rows; j++)
{
float f2 = CSumDst.at<float>(j, 0);
if (fMinV > fabs(f2-f1))
{
fMinV = fabs(f2 - f1);
nMinLoc = j;
}
}
lut.at<uchar>(i) = nMinLoc;
}
}
#include "../include/baseOps.h"
#include <iostream>
#include <string>
#include "../include/opencv400/opencv2/opencv.hpp"
#include "../include/opencv400/opencv2/core/core.hpp"
#include "../include/opencv400/opencv/highgui.h"
#include "windows.h"
int main()
{
SetCurrentDirectoryToExePath();
cv::Mat srcimg = cv::imread("../images/6.jpg");
cv::cvtColor(srcimg, srcimg, cv::COLOR_BGR2GRAY);
cv::Mat srcHist;
CalcNormalizedHist1D(srcimg, srcHist);
cv::Mat csumSrc;
CalcCSum(srcHist, csumSrc);
cv::Mat dstimg = cv::imread("../images/7.jpg");
cv::cvtColor(dstimg, dstimg, cv::COLOR_BGR2GRAY);
cv::Mat dstHist;
CalcNormalizedHist1D(dstimg, dstHist);
cv::Mat csumDst;
CalcCSum(dstHist, csumDst);
cv::Mat lut;
CreateHSLT(csumSrc, csumDst, lut);
cv::Mat res;
cv::LUT(srcimg, lut, res);
cv::Mat resHist;
CalcNormalizedHist1D(res, resHist);
ShowHist("Result Hist", resHist);
cv::waitKey();
return 0;
}