直方图匹配

直方图匹配又称为直方图规定化,是指将一幅图像的直方图变成规定形状的直方图而进行的图像增强方法。 即将某幅影像或某一区域的直方图匹配到另一幅影像上。使两幅影像的色调保持一致。可以在单波段影像直方图之间进行匹配,也可以对多波段影像进行同时匹配。两幅图像比对前,通常要使其直方图形式一致。

直方图规定化,也叫做直方图匹配,用于将图像变换为某一特定的灰度分布,也就是其目的的灰度直方图是已知的。这其实和均衡化很类似,均衡化后的灰度直方图也是已知的,是一个均匀分布的直方图;而规定化后的直方图可以随意的指定,也就是在执行规定化操作时,首先要知道变换后的灰度直方图,这样才能确定变换函数。规定化操作能够有目的的增强某个灰度区间,相比于,均衡化操作,规定化多了一个输入,但是其变换后的结果也更灵活。

直方图规定化的实现步骤如下:

  • 计算原图像的累积直方图
  • 计算规定直方图的累积直方图
  • 计算两累积直方图的差值的绝对值
  • 根据累积直方图最小差值建立灰度级的映射
灰度值 0 1 2 3 4 5 6 7
原图累计概率 0.19 0.44 0.65 0.81 0.89 0.95 0.98 1.00
目标图累计概率 0 0 0 0.15 0.35 0.65 0.85 1.00
映射差值最小 0->3 1->4 2->5 3->6 4->6 5->7 6->7 7->7

代码实现:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import cv2 as cv
import matplotlib.pyplot as plt;
import numpy as np

"""
直方图匹配: 让一张图参考另一张图, 让他们的色调保持一致
    步骤:
        计算原图累计直方图
        计算参考图的累计直方图
        计算两个累计直方图的差异
        生成原图和参考图之间的颜色映射
"""
# 计算单通道图像 累计概率
def getAccumulateRatios(img):

    height = img.shape[0]
    width = img.shape[1]

    # 1.计算直方图
    hist = cv.calcHist([img],[0],None,[256],[0,255])
    # print(hist)
    # plt.plot(hist)
    # plt.show()

    # 2.计算每个灰度值出现的概率
    ratios = hist/(height*width);

    # 3. 计算累计概率
    sumRatios = np.zeros(256,np.float)
    sum1=0;
    for i,r in enumerate(ratios):
        sum1 = sum1 + r;
        sumRatios[i] = sum1;

    # 4. 绘制累计概率直方图
    # x = np.linspace(0,255,256);
    # plt.bar(x,sumRatios)
    # plt.show()

    return sumRatios;


"""
 传入进来的数据仍然上单通道数据
    1. 计算累计直方图的差异
        找到最小的差异
    2. 获取颜色映射    
"""
def map_color(src_channel,refer_channel):
    # src 单通道累计直方图
    src_sumRatios = getAccumulateRatios(src_channel);
    # refer 单通道累计直方图
    refer_sumRatios = getAccumulateRatios(refer_channel);

    colorMaps = np.zeros(256,np.uint8);

    # 遍历原图每一个灰度的累计概率
    for i,srcRatio in enumerate(src_sumRatios):
        # 得到 i: 灰度值  ratio :累计概率  0  0.19
        min = 100;
        referColor = 0;
        for j,referRatio in enumerate(refer_sumRatios):
            # j: 表示参考图的灰度值, referRatio累计概率
            diff = np.abs(srcRatio - referRatio)
            if diff < min:
                # 更新最小值
                min = diff;
                referColor = j;

        # 0 ---> referColor
        colorMaps[i] = referColor;

    # print(colorMaps)
    return colorMaps;


""" 完成单通道直方图匹配  """
def oneChannelMatch(src_channel,refer_channel):
    # 单通道颜色值映射
    colorMaps = map_color(src_channel, refer_channel);

    # 绘制原图直方图
    # cv.imshow("src",src_channel)
    # plt.hist(src_channel.ravel(),bins=256,color="blue")
    # plt.show()
    # 绘制参考图直方图
    # cv.imshow("refer", refer_channel)
    # plt.hist(refer_channel.ravel(), bins=256,color="green")
    # plt.show()

    one_channel = src_channel
    height = one_channel.shape[0]
    width = one_channel.shape[1]
    for row in range(height):
        for col in range(width):
            # 获取原来的灰度值
            gray = one_channel[row, col];
            # 去颜色映射表中查找新的颜色值
            referColor = colorMaps[gray];
            # 替换为新的颜色值
            one_channel[row, col] = referColor;

    # 绘制生成之后的直方图
    # cv.imshow("dst", one_channel)
    # plt.hist(refer_channel.ravel(), bins=256, color="red")
    # plt.show()

    return one_channel

if __name__ == '__main__':
    src = cv.imread("../img/1.jpg",cv.IMREAD_COLOR);
    cv.imshow("src",src)
    refer = cv.imread("../img/2.jpg",cv.IMREAD_COLOR);
    cv.imshow("refer",refer)

    # 先计算src的累计直方图 , 计算单通道
    src_channels = cv.split(src);

    # 先计算refer的累计直方图 , 计算单通道
    refer_channels = cv.split(refer);

    dst_channel0 = oneChannelMatch(src_channels[0],refer_channels[0])
    dst_channel1 = oneChannelMatch(src_channels[1],refer_channels[1])
    dst_channel2 = oneChannelMatch(src_channels[2],refer_channels[2])

    dst = cv.merge([dst_channel0,dst_channel1,dst_channel2])
    cv.imshow("dst",dst);


    cv.waitKey(0)
    cv.destroyAllWindows()