数字图像学笔记——4. 直方图计算、线性变换、对数变换、Gamma变换
文章目录
- 灰度直方图(Gray Histogram)
- 直方图的计算方法
- 简单的图像转换方法
- 线性变换 / 图像翻转(Image Nagatives)
- 对数变换(Log Transformation)
- Gamma变换(Power-Law Transformation)
灰度直方图(Gray Histogram)
在数字图像处理中,灰度直方图是一种计算代价非常小但却很有用的工具,它概括了一幅图像的灰度级信息,它用来描述每个灰度级在图像矩阵中的像素个数或者占有率,或者出现频率。
通过直方图,我们可以很快知道一个灰度图片中有效数据的大概范围,可以通过一些简单的线性计算,将我们感兴趣的数据重新编辑到新的图片中,也可以调节一张照片的曝光量,某些教科书上称这种做法叫对比度进行调节。
当然,如果配合神经元网络,我们也可以对一些黑白照片重新上色。当然这些都是比较高阶的技巧了。
直方图调整有几种常用的方法,如线性变换、分段线性变换、伽马变换、直方图正规化、直方图均衡化、局部自适应直方图均衡化、图像翻转等,这些方法的计算代价较小,但是却产生了较为理想的效果。
直方图的计算方法
我又把这张图拿了出来,直方图的计算方式,其实就是从上到下,从左到右一个一个像素进行统计。所以假设我们对林肯的照片进行分析,绘制这张照片的直方图,那么在X轴的方向,就是强度信息,也就是灰度值 [ 0 , 255 ] [0, 255] [0,255],而它的Y轴方向,就是频率信息,或者说是各像素点值的出现率。
def calculate_hist(img):
# create a matrix to hold histogram information
histogram = np.zeros(256, dtype=np.uint64)
# iterate each pixel
row, col = img.shape
for r in range(row):
for c in range(col):
histogram[img[r, c]] = histogram[img[r, c]] + 1
# return to caller
return histogram
我们对结果进行比对,看看是否一致:
# load data from file
img = cv2.imread("Data/lincoln.jpg", cv2.IMREAD_GRAYSCALE)
my_hist = calculate_hist(img)
# show
show_images(img.reshape(-1), my_hist)
输出的效果:
左侧是我们的计算结果,而右侧是通过matplotlib进行统计的结果,我们可以看到结果是一样的。通过直方图,我们知道我们感兴趣的数据,主要集中在 [ 0 , 100 ] [0, 100] [0,100]和 [ 160 , 255 ] [160, 255] [160,255]这两个范围区间内,那么这两个区间内都包含哪些有趣的图像信息呢?
简单的图像转换方法
线性变换 / 图像翻转(Image Nagatives)
线性变换,又或者叫图像翻转,通常被应用于X光照片这类图像的处理。由于我们兴趣区域的数据多处于低密度区域,而这部分数据如果直接显示通常较难观察到(low intensity),例如X光来说,低密度的细节通常很难进行观察,而通过线性转换,则可以得到一些用肉眼不太容易注意到的细节。
比如这张对乳腺的X光照片,转换后的右侧图片中黑色斑块的位置,可能预示患者已经有了肿块,可能需要进一步的细致检查。
假设图像的灰阶为 [ 0 , 255 ] [0, 255] [0,255]这个范围,那么它的线性转换计算方式可以简单的表示为:
I c = 255 − I o I_c = 255 - I_o Ic=255−Io
其中 I c I_c Ic表示为转换后的图像灰阶, I o I_o Io表示为原始图像灰阶。在冈萨雷斯的教材上,这个公式表示为: s = L − 1 − r s = L - 1 - r s=L−1−r,本质上是一样的。算法代码如果使用到 numpy 的独有特性,那么可以更简单的写成这样:
def image_negative(file: str):
image = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
negative_img = 255 - image.flatten()
negative_img = negative_img.reshape(image.shape)
show_images(image, negative_img, "Negative Image")
对数变换(Log Transformation)
由于线性变化,正如其名字线性的,所以如果很多重要的数据都挤压在低密度数据区间时,直接进行线性变化也很难看出图像的特征,那么就通过对数的方式,不成比例的“放大”图像的细节。
I c = c ⋅ l o g ( 1 + I o ) I_c = c \cdot log(1 + I_o) Ic=c⋅log(1+Io)
其中 c c c 是曲线放大系数, ( 1 + I o ) (1+I_o) (1+Io) 的目的是把 log 曲线向左移1个单位。
放大系数并无固定要求,通常根据自己实际工作的需要来调试确定。
def log_transform(file: str):
image = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
log_trans = 10 * np.log(1 + image)
log_trans = np.uint8(log_trans)
show_images(image, log_trans, "Log Transform")
Gamma变换(Power-Law Transformation)
Gamma函数实际上就是幂函数,其公式为:
I c = c ⋅ I o γ I_c = c \cdot I{_o}^\gamma Ic=c⋅Ioγ
不同的gamma值,会输出不同的映射曲线,由于我们通常只使用其第一象限内的映射,且把 [ 0 , 255 ] [0,255] [0,255]的灰阶强度,归一化后控制在 [ 0 , 1 ] [0,1] [0,1]这个区间
所以实际上的转换效果是这样:
通过gamma变换,可以有效的纠正一张过曝或欠光的图片。从图中的曲线可以得知,如果要将一张欠光的照片调整到正常的样子,可以让 γ < 1 \gamma < 1 γ<1。而对于一张过曝的照片,则可以让 γ > 1 \gamma > 1 γ>1,从而获得满意的效果。
c c c 通常为放大、缩小倍数,一般情况下,我们令其为1。
对于欠光的图片:
而对于过曝照片:
def gamma_transform(file: str, c, gamma):
image = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
# # normalization the pixels
float_image = image / 255.0
gamma_trans = c * np.power(float_image, gamma)
gamma_trans = np.uint8(gamma_trans * 255.0)
show_images(image, gamma_trans , "Gamma Transform")
还没有评论,来说两句吧...