如何在缩放图像后获得平滑的直方图?

How to obtain smooth histogram after scaling image?

我正在尝试对图像进行线性缩放,以便使用整个灰度范围。这是为了改善镜头的照明。然而,在绘制直方图时,我不知道如何获得缩放直方图,使其更平滑,因此它是一条曲线,就像离散箱所期望的那样。任何提示或要点将不胜感激。

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

img = cv.imread(r'/Users/harold/Documents/Academia/Nottingham Uni/Year 4/ImageProcessing/Imaging_Task_Sheet/PointImage.jpeg', cv.IMREAD_GRAYSCALE)

img_s = img/255
img_s = img_s / np.max(img_s)
img_s = img_s*255

histogram = cv.calcHist([img], [0], None, [256], [0, 256])
histogram1 = cv.calcHist([img_s.astype('uint8')], [0], None, [256], [0, 256])

plt.figure()
plt.title("Grayscale Histogram")
plt.xlabel("grayscale value")
plt.ylabel("pixels")

plt.plot(histogram, label='Original Image')  # <- or here
plt.plot(histogram1, label='Equalised Image')  # <- or here

生成的直方图是:

出自这张图片:

如果您线性缩放图像,我不确定这是否可行。但是,您可以试试 OpenCV 的 Contrast Limited Adaptive Histogram Equalization

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

img = cv.imread('3NKTJ.jpg', cv.IMREAD_GRAYSCALE)
img_clahe = img.copy()
img_clahe = img_clahe/np.max(img_clahe)
img_clahe = (255*img_clahe).astype(np.uint8)

clahe = cv.createCLAHE(clipLimit=5, tileGridSize=(3,3))
img_clahe = clahe.apply(img_clahe)

img_s = img/255
img_s = img_s / np.max(img_s)
img_s = img_s*255

histogram = cv.calcHist([img], [0], None, [256], [0, 256])
histogram1 = cv.calcHist([img_s.astype('uint8')], [0], None, [256], [0, 256])
histogram2 = cv.calcHist([img_clahe.astype('uint8')], [0], None, [256], [0, 256])

plt.figure(dpi=100)
plt.title("Grayscale Histogram")
plt.xlabel("grayscale value")
plt.ylabel("pixels")

plt.plot(histogram, label='Original Image')  # <- or here
plt.plot(histogram1, label='Equalised Image')  # <- or here
plt.plot(histogram2, label='CLAHE Image')
plt.legend()
plt.show()

您可以使用 clipLimittileGridSize 来获得您想要的图像。 default values40.0(8, 8)

您应该阅读有关 gamma correction 的内容:

使用此 中的代码,它使用自动方式计算 gamma 值,您会得到以下结果:

方法一:

方法二:

校正后的图像直方图(方法 2)如下所示:

**编辑2:** 或者您可以使用线性方法在 0 到 255 之间重新调整每个通道。使用此代码:

def apply_white_balance_single_channel(img, low_ratio=0.001, high_ratio=0.001):
    hist_size = 256
    hist = cv2.calcHist([img], [0], None, [hist_size], [0,hist_size])
    acc = np.cumsum(hist)
    low_limit = low_ratio * acc[-1] 
    high_limit = high_ratio * acc[-1]
    min_gray = 0
    while acc[min_gray] < low_limit and min_gray + 1 < hist_size: 
        min_gray += 1
    max_gray = hist_size - 1
    while acc[max_gray] >= acc[-1] - high_limit and max_gray > min_gray:
        max_gray -= 1
    input_range = max_gray - min_gray
    alpha = (hist_size - 1) / input_range
    beta = -min_gray * alpha
    return  (alpha * img + beta).clip(0,255).astype(np.uint8)

def apply_white_balance_multi_channel(img, low_ratio=0.001, high_ratio=0.001):
    channels = cv2.split(img)
    return cv2.merge([apply_white_balance_single_channel(ch, low_ratio, high_ratio) for ch in channels])

结果如下:

重新缩放的图像:

直方图:

编辑3

或者您可以使用更简单的版本,使用简单的最小-最大归一化来进行线性重新缩放:

def apply_white_balance_multi_channel_min_max(img):
    channels = cv2.split(img)
    return cv2.merge([cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX) for ch in channels])

我想你的想法是通过你的点的样条曲线。方法如下:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

from scipy import interpolate

img = cv.imread(r'3NKTJ.jpg', cv.IMREAD_GRAYSCALE)

img_s = img/255
img_s = img_s / np.max(img_s)
img_s = img_s*255

histogram = cv.calcHist([img], [0], None, [256], [0, 256])
histogram1 = cv.calcHist([img_s.astype('uint8')], [0], None, [256], [0, 256])

x=np.linspace(0,len(histogram1),len(histogram1)) # x: 0 --> 255 with step=1

X=np.where(histogram1>0)[0] # extract bins with non-zero histogram1 values
Y=histogram1[X] # the corresponding Y values

F=interpolate.splrep(X, Y)   # spline representation of (X,Y)
Ynew = interpolate.splev(x, F) # calculate interpolated Ynew

plt.figure()
plt.title("Grayscale Histogram")
plt.xlabel("grayscale value")
plt.ylabel("pixels")

plt.plot(histogram, label='Original Image')  # <- or here
plt.plot(histogram1, label='Equalised Image')  # <- or here
plt.plot(x,Ynew, label='spline interpolation of Equalised Image')

下面,结果:

此致, 斯蒂芬

I am trying to linearly scale an image so the whole greyscale range is used

为了增加图像的动态,有一种常用的图像处理方法叫做“直方图均衡化”(参见:cv.equalizeHist()doc

为了获得更好的结果,您应该移除图像的极值,以移除 under/over 曝光。

为此,在均衡图像上:

  • 计算直方图的累积和
  • 找到第一个强度低于 rate 和第一个强度高于 1-rate
  • trim 所有图像强度为这 2 个值。

这是一个示例代码:

def equalize_hist(data: np.ndarray, rate: float) -> np.ndarray:
    data = cv.equalizeHist(data.astype(np.uint8))

    hist = np.cumsum(np.histogram(data, 255)[0])
    lowest_value = np.where((rate * hist[-1]) <= hist)[0][0]
    highest_value = np.where(((1 - rate) * hist[-1]) >= hist)[0][-1]

    sdata[self.data < lowest_value] = lowest_value
    data[self.data > highest_value] = highest_value

    return data

rate 的常用值为 0.05。当您增加速率时,您会增加图像的动态,但会丢失信息。您不能将 rate 提高到 0.5 以上。