第一章 图像预处理 (1)对比度增强 (直方图)

1. 统计直方图

我的理解,灰度直方图就是统计整张图片的灰度值。比如说对于一张8位图,其灰度级数为0~255,那么灰度直方图便是统计灰度依次区0~255的个数,比如说灰度是127的像素个数,灰度是0的个数等等。

下面是用Python实现的代码:

  • 定义函数
def calGrayHist(image):
    r,c=image.shape#灰度图尺寸

    #创建一个一维数组grayHist,长度为255,用其序列表示灰度值
    grayHist= np.zeros([256],np.uint64)
    for i in range(r):
        for j in range(c):
            #遍历所有元素,把其灰度值所代表的序列指向的数组grayHist累加
            grayHist[image[i][j]]+=1
    return grayHist

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

#读取图片
image= cv2.imread('D:/codes/shan.jpg',0)
#计算灰度直方图
grayHist= calGrayHist(image)

#画出直方图
x_range = range(256)
plt.plot(x_range,grayHist,'r',linewidth =2,c='black')

#设置坐标轴范围
y_maxValue=np.max(grayHist)
plt.axis([0,255,0,y_maxValue])

#设置坐标标签
plt.xlabel('graylevel')
plt.ylabel('number of pixels')

#显示灰度直方图
plt.show()

其原图如下,图片来源于互联网,若有版权问题请联系我。
这里写图片描述

非常漂亮的一张风景图,它的灰度直方图如下:
这里写图片描述

  • 也可以用matplotlib本身的计算直方图的函数:
import numpy as np
import cv2
import matplotlib.pyplot as plt

#读取图片
image= cv2.imread('D:/codes/shan.jpg',0)

r,c=image.shape#灰度图尺寸

#将二维图像矩阵变为为一维,便于计算
pixelSequence=image.reshape([r*c,])

#组数
numberBins=256

#计算灰度直方图
histogram,bins,patch=plt.hist(pixelSequence,numberBins,facecolor='black',histtype='bar')

#设置坐标轴范围
y_maxValue=np.max(histogram)
plt.axis([0,255,0,y_maxValue])

#设置坐标标签
plt.xlabel('graylevel')
plt.ylabel('number of pixels')

#显示图像
plt.show()

其灰度直方图如下:
这里写图片描述
这里解释下plt.hist()函数,其调用方式为:

n, bins, patches = plt.hist(arr, bins=10, normed=0, facecolor='black', edgecolor='black',alpha=1,histtype='bar')

hist的参数非常多,但常用的就这六个,只有第一个是必须的,后面四个可选:

  1. arr: 需要计算直方图的一维数组

  2. bins: 直方图的柱数,可选项,默认为10

  3. normed: 是否将得到的直方图向量归一化。默认为0

  4. facecolor: 直方图颜色

  5. edgecolor: 直方图边框颜色

  6. alpha: 透明度

  7. histtype: 直方图类型,‘bar’, ‘barstacked’, ‘step’, ‘stepfilled’

返回值 :

  1. n: 直方图向量,是否归一化由参数normed设定

  2. bins: 返回各个bin的区间范围

  3. patches: 返回每个bin里面包含的数据,是一个list

    • 当然也可以用opencv统计直方图函数 cv2.calcHist()来统计一张图的直方图。其函数用法如下:
cv2:calcHist(images; channels; mask; histSize; ranges[; hist[; accumulate]])
  1. images:原图像(图像格式为 uint8 或 float32)。当传入函数时应该用中括号 [] 括起来,例如: [img]。
  2. channels: 同样需要用中括号 [ ] 括起来,它会告诉函数我们要统计那幅图像的直方图。如果输入图像是灰度图,它的值就是 [0];如果是彩色图像的话,传入的参数可以是 [0], [1], [2] 它们分别对应着通道 B, G, R。
  3. mask: 掩模图像。要统计整幅图像的直方图就把它设为 None。但是如果你想统计图像某一部分的直方图的话,你就需要制作一个掩模图像,并使用它。
  4. histSize:BIN 的数目。也应该用中括号括起来,例如: [256]
  5. ranges: 像素值范围,通常为 [0, 256]

那么来简单试下:

import cv2
from matplotlib import pyplot as plt
img= cv2.imread('D:/codes/shan.jpg')#读取图像

color = ('b','g','r')
# 对一个列表或数组既要遍历索引又要遍历元素时
# 使用内置 enumerrate 函数会有更加直接,优美的做法
#enumerate 会将数组或列表组成一个索引序列。
# 使我们再获取索引和索引内容的时候更加方便
for i,col in enumerate(color):
    histr = cv2.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color = col)
    plt.xlimplt.xlim([0,256])#设置x坐标范围
plt.show()

每次循环的histr是一个 256x1 的数组,每一个值代表了与该通道像素值对应的像素点数
目。这里用的是matplotlib来画图,用OpenCV会比较麻烦。
其结果如下:这里写图片描述

  • 当然也可以用Numpy的中函数np.histogram() 帮我们统计直方图,例如:
#img.ravel() 将图像转成一维数组,这里没有中括号。
hist,bins = np.histogram(img.ravel(),256,[0,256])

hist 与上面计算的一样。但是这里的 bins 是 257,因为 Numpy 计算bins 的方式为: 0-0.99,1-1.99,2-2.99 等。所以最后一个范围是 255-255.99。为了表示它,所以在 bins 的结尾加上了 256。但是我们不需要 256,到 255就够了。

当然Numpy 还 有 一 个 函 数 np.bincount(), 所 以 对 于 一 维 直 方 图, 我 们 最 好 使 用 这 个函 数。 它 的 运 行 速 度 是np.histgram 的 十 倍。OpenCV 的函数要比np.histgram() 快 40 倍。

- 线性变换

直白来讲,如果一张图灰度级数为[50,100],那么可以通过线性变换将其进行拉伸。
对于一个输入图像I,宽W,高H,输出为O,其线性变换为:
O(r,c)=a*I(r,c)+b,0<=r

import cv2
import numpy as np

image= cv2.imread('D:/codes/shan.jpg',0)

MAX_VALUE = 120
value = 120

#调整对比度后,图像的效果显示窗口
cv2.namedWindow("contrast",cv2.WND_PROP_AUTOSIZE)
#调整系数,观察图像的变化
def callback_contrast(_value):
    #通过线性运算,调整图像对比度
    a = float(_value)/40.0
    #线性变换
    contrastImage = a*image
    #数据截断,大于255的数据截为255
    contrastImage[contrastImage>255]=255
    #数据类型转换
    contrastImage = np.round(contrastImage)#转为整数
    contrastImage = contrastImage.astype(np.uint8)#装维uint8类型的数据
    cv2.imshow("contrast",contrastImage)#显示
    cv2.imwrite("contrast.jpg",contrastImage)#写
callback_contrast(value)
cv2.createTrackbar("value","contrast",value,MAX_VALUE,callback_contrast)#创建一个滑动条
cv2.waitKey(0)
cv2.destroyAllWindows()

其效果如下:
value=30

value=60

3. 直方图正规化

设输入图像为I,高H,宽W。那么可以用I(r,c)表示r行c列的像素值。若将I中最小灰度级记为Imin,最大灰度级记为Imax,则I∈[Imin,Imax]。为使输出图像O灰度级范围为[Omin,Omax],可做如下映射关系:

这里写图片描述

其中r∈[0,H),c∈[0,W)。这个过程称为直方图正规化。由于(I(r,c)-Imin)/(Imax-Imin)∈[Omin,Omax],故而O(r,c)∈[Omin,Omax],一般令Omin=0,Omax=255.所以可以认为直方图正规化是一种自动选取a和b的值得线性变换方法。其中:
这里写图片描述
废话不说,上代码

import numpy as np
import cv2
import matplotlib.pyplot as plt
#直方图正规化
#1、若输入是 8 位图 ,一般设置 O_min = 0,O_max = 255
#2、若输入的是归一化的图像,一般设置 O_min = 0,O_max = 1
def histNormalized(InputImage,O_min = 0,O_max = 255):
    #得到输入图像的最小灰度值
    I_min = np.min(InputImage)

    #得到输入图像的最大灰度值
    I_max = np.max(InputImage)

    #得到输入图像的宽高
    rows,cols = InputImage.shape

    #输出图像
    OutputImage = np.zeros(InputImage.shape,np.float32)
    #输出图像的映射
    cofficient = float(O_max - O_min)/float(I_max - I_min)
    for r in range(rows):
        for c in range(cols):
            OutputImage[r][c] = cofficient*( InputImage[r][c] - I_min) + O_min
    return OutputImage

#读取图像
image= cv2.imread('D:/codes/shan.jpg',0)

#显示原图
cv2.imshow("image",image)
#直方图正规化
histNormResult = histNormalized(image)
#数据类型转换,灰度级显示
histNormResult = np.round(histNormResult)
histNormResult = histNormResult.astype(np.uint8)
#显示直方图正规化的图片
cv2.imshow("histNormlized",histNormResult)
cv2.imwrite("histNormResult.jpg",histNormResult)
'''
#如果输入图像是归一化的图像
image_0_1 = image/255.0
#直方图正规化
histNormResult = histNormalized(image_0_1,0,1)
#保存结果
histNormResult = 255.0*histNormResult
histNormResult = np.round(histNormResult)
histNormResult = histNormResult.astype(np.uint8)
'''
#显示直方图正规化后图片的灰度直方图
#组数
numberBins = 256
#计算灰度直方图
rows,cols = image.shape
histNormResultSeq = histNormResult.reshape([rows*cols,])
histogram,bins,patch_image= plt.hist(histNormResultSeq,numberBins,facecolor='black',histtype='bar')
#设置坐标轴的标签
plt.xlabel(u"gray Level")
plt.ylabel(u"number of pixels")
#设置坐标轴的范围
y_maxValue = np.max(histogram)
plt.axis([0,255,0,y_maxValue])
plt.show()
cv2.waitKey(0)
cv2.destroyAllWindows()

由于原图采光还可以,灰度图本身效果还可以,做正规划后的灰度图和原图差别不大,就不放出来了。下面是正规划后的灰度直方图:
这里写图片描述
差别确实不大,读者可以尝试用其他图片。

用OpenCV可以这么写:

import cv2 as cv
import numpy as np

img= cv.imread('D:/codes/111.png',cv.IMREAD_ANYCOLOR)#读取图像
normalizedImg = np.zeros((800, 800))
normalizedImg =cv.normalize(img,  normalizedImg, 0, 255, cv.NORM_MINMAX)
cv.imshow('dst_rt', normalizedImg)
cv.waitKey(0)
cv.destroyAllWindows()

4. 伽马变换

设输入图像为I,高H,宽W。现将灰度值归一化到[0,1]之间。那么可以用I(r,c)表示r行c列的像素值,输出标记为O,伽马变换就是令O(r,c)=I(r,c)^γ,其中r∈[0,H),c∈[0,W)。
当γ=1时,图像不变。如果图像偏暗,可令γ∈(0,1),增强图像对比度,反之令γ>1降低对比度。

#伽马变换
import cv2
import numpy as np


image= cv2.imread('D:/codes/shan.jpg',0)

MAX_VALUE = 200
value = 40
segValue = float(value)

#伽马调整需要先将图像归一化
image_0_1 = image/255.0

#伽马调整后的图像显示窗口
cv2.namedWindow("gamma_contrast",cv2.WND_PROP_AUTOSIZE)

#调整 gamma 值,观察图像的变换
def callback_contrast(_value):
    gamma = float(_value)/segValue
    contrastImage = np.power(image_0_1,gamma)
    cv2.imshow("gamma_contrast",contrastImage)
    #保存伽马调整的结果
    contrastImage*=255
    contrastImage = np.round(contrastImage)
    contrastImage = contrastImage.astype(np.uint8)
    cv2.imwrite("gamma.jpg",contrastImage)
callback_contrast(value)
cv2.createTrackbar("value","gamma_contrast",value,MAX_VALUE,callback_contrast)
cv2.waitKey(0)
cv2.destroyAllWindows()

其效果如下:
value=117

- 5全局直方图均衡化

全局直方图均衡化是对输入图像I进行改变,是的输出图像O的灰度直方图是“平”的,即每一个灰度级的像素点个数是“相等”的。这里的相等并不是严格意义上的等于,而是约等于。对于直方图均衡化的实现主要分为四个步骤:
- 第一步:计算图像的灰度直方图
- 第二步:计算灰度直方图的累加直方图
- 第三部:根据累加直方图和直方图均衡化原理,得到输入灰度级和输出图像的每一个像素的灰度级之间的映射关系
- 第四步:根据第三步得到的灰度映射关系,循环得到输出图像的每一个像素的灰度级。

#全局直方图均衡化
import numpy as np
import cv2
import math
import matplotlib.pyplot as plt
#计算图像灰度直方图
def calcGrayHist(image):
    #灰度图像矩阵的宽高
    rows,cols = image.shape
    #存储灰度直方图
    grayHist = np.zeros([256],np.uint32)
    for r in range(rows):
        for c in range(cols):
            grayHist[image[r][c]] +=1
    return grayHist
#直方图均衡化
def equalHist(image):
    #灰度图像矩阵的宽高
    rows,cols = image.shape
    #计算灰度直方图
    grayHist = calcGrayHist(image)
    #计算累积灰度直方图
    zeroCumuMoment = np.zeros([256],np.uint32)
    for p in range(256):
        if p == 0:
            zeroCumuMoment[p] = grayHist[0]
        else:
            zeroCumuMoment[p] = zeroCumuMoment[p-1] + grayHist[p]
    #根据直方图均衡化得到的输入灰度级和输出灰度级的映射
    outPut_q = np.zeros([256],np.uint8)
    cofficient = 256.0/(rows*cols)
    for p in range(256):
        q = cofficient* float(zeroCumuMoment[p]) -1
        if q >= 0:
            outPut_q[p] = math.floor(q)
        else:
            outPut_q[p] = 0
    #得到直方图均衡化后的图像
    equalHistImage  = np.zeros(image.shape,np.uint8)
    for r in range(rows):
        for c in range(cols):
            equalHistImage[r][c] = outPut_q[image[r][c]]
    return equalHistImage

#读取图像
image= cv2.imread('D:/codes/shan.jpg',0)

#显示原图像
cv2.imshow("image",image)
#直方图均衡化
result = equalHist(image)
cv2.imshow("equalHist",result)
cv2.imwrite("equalHist.jpg",result)
#直方图均衡话后的灰度直方图
#组数
numberBins = 256
#计算灰度直方图
rows,cols = image.shape
histEqualResultSeq = result.reshape([rows*cols,])
histogram,bins,patch_image= plt.hist(histEqualResultSeq,numberBins,facecolor='black',histtype='bar')
#设置坐标轴的标签
plt.xlabel(u"gray Level")
plt.ylabel(u"number of pixels")
#设置坐标轴的范围
y_maxValue = np.max(histogram)
plt.axis([0,255,0,y_maxValue])
plt.show()
cv2.waitKey(0)
cv2.destroyAllWindows()

全局直方图均衡化

6限制对比度自适应直方图均衡化

自适应直方图均衡化首先将图像划分为不重叠的区域块,然后对每一个块分别进行直方图均衡化。但当有噪声噪声会被放大,故而提出“限制对比度”,如果直方图的bin超过提前预定设好的“限制对比度”,那么会被裁剪,然后将裁减的部分均匀分不给其他bin,这样就重构了直方图。


import cv2

image= cv2.imread('D:/codes/shan.jpg',0)#读取图像


 #创建 CLAHE  对象
clahe = cv2.createCLAHE(clipLimit=1.0,tileGridSize=(28,28))
#限制对比度的自适应阈值均衡化
dst = clahe.apply(image)
#显示
cv2.imshow("src",image)
cv2.imshow("clahe",dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV提供的函数createCLAHE构建指向CLAHE对象的指针,其中默认设置“限制对比度”为40,块的大小为8×8。
效果如下:
这里写图片描述

  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值