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

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

  1. 计算原图像的累积直方图
  2. 计算规定直方图的累积直方图
  3. 计算两累积直方图的差值的绝对值
  4. 根据累积直方图最小差值建立灰度级的映射

注意:彩色图像的直方图匹配只是先分解通道,对各个通道分别直方图规定化后再组合成彩色图像即可。

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])
    results = []
	fro i in range(3):
		res = oneChannelMatch(src_channels[i],refer_channels[i])
		reslut.append(res)
    dst = cv.merge(results)
    cv.imshow("dst",dst);


    cv.waitKey(0)
    cv.destroyAllWindows()