OPENCV(python)--初学之路(十)

一前言

今天更新时间稍晚,有些事耽误了。我已经好的差不多了,以后会正常更新的。

二主要内容--直方图

直方图 - 1:查找,绘图,分析!

那么直方图是什么?您可以将直方图视为图形或曲线图,从而使您对图像的强度分布有一个整体的了解。它是在X轴上具有像素值(不总是从0到255的范围),在Y轴上具有图像中相应像素数的图。

这只是理解图像的另一种方式。通过查看图像的直方图,您可以直观地了解该图像的对比度,亮度,强度分布等。当今几乎所有图像处理工具都提供直方图功能。以下是 剑桥彩色网站 上的图片,建议您访问该网站以获取更多详细信息。

可以看到图像及其直方图。(请记住,此直方图是针对灰度图像而非彩色图像绘制的)。直方图的左侧区域显示图像中较暗像素的数量,而右侧区域则显示较亮像素的数量。从直方图中,您可以看到暗区域多于亮区域,中间值的数量(中间值的像素值,例如127附近)非常少

查找直方图

现在我们有了一个关于直方图的想法,我们可以研究如何找到它。OpenCV和Numpy都为此内置了功能。在使用这些功能之前,我们需要了解一些与直方图有关的术语。

BINS :上面的直方图显示每个像素值的像素数,即从0到255。即,您需要256个值来显示上面的直方图。但是考虑一下,如果您不需要分别找到所有像素值的像素数,而是找到像素值间隔中的像素数怎么办?例如,您需要找到介于0到15之间,然后16到31之间,...,240到255之间的像素数。您只需要16个值即可表示直方图。这就是在 OpenCV直方图教程 中给出的示例中所显示的内容。

因此,您要做的就是将整个直方图分成16个子部分,每个子部分的值就是其中所有像素数的总和。每个子部分都称为"BIN"。在第一种情况下,bin的数量为256个(每个像素一个),而在第二种情况下,bin的数量仅为16个。BINS由OpenCV文档中的 histSize 术语表示。

DIMS :这是我们为其收集数据的参数的数量。在这种情况下,我们仅收集关于强度值的一件事的数据。所以这里是1。

范围 :这是您要测量的强度值的范围。通常,它是[0,256],即所有强度值

1. OpenCV中的直方图计算

因此,现在我们使用 cv.calcHist() 函数查找直方图。让我们熟悉一下函数及其参数:

hist = cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])

主要参数:

  • images: 输入图像列表,用方括号括起来

  • channels: 要计算直方图的通道索引列表

    • 灰度图: [0]

    • 彩色图: [0](蓝), [1](绿), [2](红)

  • mask: 掩码图像(可选)

    • None: 计算整个图像的直方图

    • 指定掩码: 只计算掩码区域

  • histSize: 每个维度的直方图大小(bin的数量)

    • 通常用方括号表示,如 [256]
  • ranges: 像素值范围

    • 灰度: [0, 256]

    • 彩色: [0, 256, 0, 256, 0, 256]

2. Numpy中的直方图计算

Numpy还为您提供了一个函数 np.histogram() 。因此,您可以在下面的行尝试代替 calcHist() 函数:

复制代码
hist,bins = np.histogram(img.ravel(),256,[0,256])

hist与我们之前计算的相同。但是bin将具有257个元素,因为Numpy计算出bin的范围为0-0.99、1-1.99、2-2.99等。因此最终范围为255-255.99。为了表示这一点,他们还在料箱末端添加了256。但是我们不需要256。最多255就足够了。

也可以看看 Numpy还有另一个函数 np.bincount() ,它比np.histogram()快10倍左右。因此,对于一维直方图,您可以更好地尝试一下。不要忘记在np.bincount中设置minlength = 256。例如,hist = np.bincount(img.ravel(),minlength = 256)

绘制直方图

有两种方法, 1. 简短方法:使用Matplotlib绘图功能 2. 很长的路要走:使用OpenCV绘图功能

1.使用Matplotlib

Matplotlib带有直方图绘图功能:matplotlib.pyplot.hist()

它直接找到直方图并将其绘制。您无需使用 calcHist() 或 np.histogram() 函数来查找直方图。请参见下面的代码:

python 复制代码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

# 读取灰度图像
img = cv.imread(r'D:\python_code\pic\test.jpg', 0)

# 绘制直方图
plt.hist(img.ravel(), 256, [0, 256])
plt.title('Image Histogram')
plt.xlabel('Pixel Value')
plt.ylabel('Frequency')
plt.show()
 

或者,您可以使用matplotlib的法线图,这对于BGR图是很好的。为此,您需要首先找到直方图数据。试试下面的代码:

python 复制代码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

# 读取图像文件
img = cv.imread('home.jpg')

# 定义颜色通道标记
color = ('b','g','r')

# 遍历RGB三通道并计算直方图
for i,col in enumerate(color):
    histr = cv.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color = col)
    plt.xlim([0,256])

# 显示直方图
plt.show()
 
遮罩的应用

我们使用 cv.calcHist() 查找完整图像的直方图。如果要查找图像某些区域的直方图怎么办?只需在要查找直方图的区域上创建白色的蒙版图像,否则创建黑色。然后通过这个作为面具。

代码如下

python 复制代码
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 读取灰度图像
img = cv.imread(r'D:\python_code\pic\test.jpg', 0)

# 创建矩形掩膜区域
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255

# 应用掩膜获取感兴趣区域
masked_img = cv.bitwise_and(img, img, mask=mask)

# 计算全图和掩膜区域的直方图
hist_full = cv.calcHist([img], [0], None, [256], [0,256])
hist_mask = cv.calcHist([img], [0], mask, [256], [0,256])

# 创建2x2子图布局
plt.subplot(221), plt.imshow(img, 'gray'), plt.title('Original Image')
plt.subplot(222), plt.imshow(mask, 'gray'), plt.title('Mask')
plt.subplot(223), plt.imshow(masked_img, 'gray'), plt.title('Masked Image')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask), plt.title('Histogram Comparison')
plt.xlim([0,256])
plt.show()
 

直方图 - 2:直方图均衡

我们将学习直方图均衡化的概念,并将其用于改善图像的对比度。 理论 考虑一个图像,其像素值仅限于特定的值范围。例如,较亮的图像会将所有像素限制在较高的值。但是,好的图像将具有来自图像所有区域的像素。因此,您需要将此直方图拉伸到两端(如下图所示,来自维基百科),这就是直方图均衡化的作用(简单来说)。通常,这可以提高图像的对比度。

python 复制代码
import numpy as np
import cv2
from matplotlib import pyplot as plt

# 读取灰度图像
image_path = 'wiki.jpg'
gray_image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)

# 计算直方图和CDF
hist_values, bin_edges = np.histogram(gray_image.flatten(), bins=256, range=[0, 256])
cumulative_dist = hist_values.cumsum()
normalized_cdf = cumulative_dist * hist_values.max() / cumulative_dist.max()

# 创建可视化图形
plt.figure(figsize=(10, 6))
plt.plot(normalized_cdf, color='blue', label='CDF')
plt.hist(gray_image.flatten(), bins=256, range=[0, 256], color='red', alpha=0.7, label='Histogram')
plt.xlim([0, 256])
plt.title('Image Histogram and CDF')
plt.xlabel('Pixel Intensity')
plt.ylabel('Frequency')
plt.legend(loc='upper left')
plt.grid(True, linestyle='--', alpha=0.5)
plt.show()
 

可以看到直方图位于较亮的区域。我们需要全方位的服务。为此,我们需要一个转换函数,该函数将较亮区域中的输入像素映射到整个区域中的输出像素。这就是直方图均衡化的作用。

现在,我们找到最小的直方图值(不包括0)并应用Wiki页面中给出的直方图均衡方程。但是我在这里使用了Numpy的masked array概念数组。对于掩码数组,所有操作都在非掩码元素上执行。您可以从有关屏蔽数组的Numpy文档中了解有关此内容的更多信息

python 复制代码
import numpy as np

# 创建示例累积分布函数(CDF)数组
cdf = np.array([0, 10, 20, 30, 40, 50, 0, 70, 80, 90, 100])

# 屏蔽值为0的元素
cdf_m = np.ma.masked_equal(cdf, 0)

# 归一化到0-255范围
cdf_m = (cdf_m - cdf_m.min()) * 255 / (cdf_m.max() - cdf_m.min())

# 填充被屏蔽的值并转换为uint8类型
cdf = np.ma.filled(cdf_m, 0).astype('uint8')

print(cdf)
 

现在我们有了查找表,该表为我们提供了有关每个输入像素值的输出像素值是什么的信息。因此,我们仅应用变换。

img2 = cdf[img]

现在我们像以前一样计算它的直方图和cdf(您这样做),结果如下所示:

一个重要特征是,即使图像是较暗的图像(而不是我们使用的较亮的图像),在均衡后,我们将获得与获得的图像几乎相同的图像。结果,它被用作"参考工具",以使所有图像具有相同的照明条件。在许多情况下这很有用。例如,在人脸识别中,在训练人脸数据之前,将人脸图像进行直方图均衡,以使它们全部具有相同的光照条件。

OpenCV中的直方图均衡

OpenCV具有执行此操作的功能 cv.equalizeHist() 。它的输入只是灰度图像,输出是我们的直方图均衡图像。

下面是一个简单的代码片段,显示了它与我们使用的同一图像的用法:

此,现在您可以在不同的光照条件下拍摄不同的图像,对其进行均衡并检查结果。

当图像的直方图限制在特定区域时,直方图均衡化效果很好。在直方图覆盖较大区域(即同时存在亮像素和暗像素)的强度变化较大的地方,效果不好。请检查其他资源中的SOF链接

CLAHE(对比度受限的自适应直方图均衡)

我们刚刚看到的第一个直方图均衡化考虑了图像的整体对比度。在许多情况下,这不是一个好主意。例如,下图显示了输入图像及其在全局直方图均衡后的结果。

直方图均衡后,背景对比度确实得到了改善。但是在两个图像中比较雕像的脸。由于亮度过高,我们在那里丢失了大多数信息。这是因为它的直方图不像我们在前面的案例中所看到的那样局限于特定区域(尝试绘制输入图像的直方图,您将获得更多的直觉)。

因此,为了解决这个问题,使用了 自适应直方图均衡 。在这种情况下,图像被分成称为" tiles"的小块(在OpenCV中,tileSize默认为8x8)。然后,像往常一样对这些块中的每一个进行直方图均衡。因此,在较小的区域中,直方图将局限于一个较小的区域(除非有噪声)。如果有噪音,它将被放大。为了避免这种情况,应用了 对比度限制 。如果任何直方图bin超过指定的对比度限制(在OpenCV中默认为40),则在应用直方图均衡之前,将这些像素裁剪并均匀地分布到其他bin。均衡后,要消除图块边界中的伪影,请应用双线性插值。

下面的代码片段显示了如何在OpenCV中应用CLAHE:

python 复制代码
import numpy as np
import cv2 as cv

# 读取灰度图像
img = cv.imread('tsukuba_l.png', 0)

# 创建CLAHE对象
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))

# 应用CLAHE
cl1 = clahe.apply(img)

# 保存处理后的图像
cv.imwrite('clahe_2.jpg', cl1)
 

直方图 - 3:2D 直方图

在第一篇文章中,我们计算并绘制了一维直方图。之所以称为一维,是因​​为我们仅考虑一个特征,即像素的灰度强度值。但是在二维直方图中,您要考虑两个特征。通常,它用于查找颜色直方图,其中两个特征是每个像素的色相和饱和度值。

已经有一个python样本( samples/python/color_histogram.py )用于查找颜色直方图。我们将尝试了解如何创建这种颜色直方图,这对于理解诸如直方图反投影之类的更多主题将很有用。

OpenCV中的2D直方图

它非常简单,并且使用相同的函数 cv.calcHist() 进行计算。对于颜色直方图,我们需要将图像从BGR转换为HSV。(请记住,对于一维直方图,我们从BGR转换为灰度)。对于2D直方图,其参数将进行如下修改:

  • channels = [0,1], 因为我们需要同时处理H和S平面。
  • bins = [180,256] 对于H平面为180,对于S平面为256。
  • range= [0,180,0,256] 色相值介于0和180之间,饱和度介于0和256之间。 现在检查以下代码:
python 复制代码
b, g, r = cv.split(img)
clahe = cv 
python 复制代码
import numpy as np
import cv2 as cv

# 读取灰度图像
img = cv.imread('tsukuba_l.png', 0)

# 创建CLAHE对象
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))

# 应用CLAHE
cl1 = clahe.apply(img)

# 保存处理后的图像
cv.imwrite('clahe_2.jpg', cl1)
 

绘制2D直方图

方法-1:使用 cv.imshow()

我们得到的结果是尺寸为180x256的二维数组。因此,可以使用 cv.imshow() 函数像平常一样显示它们。它将是一幅灰度图像,除非您知道不同颜色的色相值,否则不会对其中的颜色有太多了解。

方法-2:使用Matplotlib

我们可以使用 matplotlib.pyplot.imshow() 函数绘制具有不同颜色图的2D直方图。它使我们对不同的像素密度有了更好的了解。但是,这也并不能使我们一眼就能知道是什么颜色,除非您知道不同颜色的色相值。我还是更喜欢这种方法。它简单而更好。

注意 使用此功能时,请记住,插值标记应最接近以获得更好的结果。

方法3:OpenCV示例样式!

OpenCV-Python2示例中有一个颜色直方图的示例代码( samples/python/color_histogram.py )。如果运行代码,则可以看到直方图也显示了相应的颜色。或者简单地,它输出颜色编码的直方图。其结果非常好(尽管您需要添加额外的线束)。

在该代码中,作者在HSV中创建了一个颜色图。然后将其转换为BGR。将所得的直方图图像与此颜色图相乘。他还使用一些预处理步骤来删除小的孤立像素,从而获得良好的直方图。

我将它留给读者来运行代码,对其进行分析并拥有自己的解决方法。下面是与上面相同的图像的代码输出:

  • 您可以在直方图中清楚地看到存在什么颜色,那里是蓝色,那里是黄色,并且由于棋盘而有些白色。不错!

三最后一语

今天东西较多,建议大家慢慢学,还是很重要的

我深怕自己本非美玉,

故而不敢加以刻苦琢磨,

却又半信自己是块美玉,

故又不肯庸庸碌碌,

与瓦砾为伍。

/中岛敦丨《山月记》

感谢观看,共勉!!

注:所有关于opencv的内容皆依靠https://opencv.apachecn.org/此网站,有讲的不清楚的大家可以去看看

相关推荐
Wise玩转AI1 小时前
AI智能体开发实战AutoGen篇(四)——会干活的导诊 Agent(Planner + Tools 实战)
人工智能·python·microsoft·ai智能体·autogen
AI小云1 小时前
【数据操作与可视化】Serborn绘图-单变量分布
python·数据可视化
www7691 小时前
构建企业级代码知识图谱系统:从理论到实践
人工智能
AndrewHZ1 小时前
【复杂网络分析】复杂网络分析技术在图像处理中的经典算法与应用实践
图像处理·人工智能·算法·计算机视觉·图像分割·复杂网络·图论算法
最晚的py1 小时前
机器学习--损失函数
人工智能·python·机器学习·损失函数
free-elcmacom1 小时前
机器学习入门<4>RBFN算法详解
开发语言·人工智能·python·算法·机器学习
Qinana1 小时前
当AI为你写SQL,连数据库都开始谈恋爱了
人工智能·python·sql
严文文-Chris1 小时前
神经网络的组成有哪些?激活函数是什么?有什么作用?
人工智能·深度学习·神经网络
sdyeswlw1 小时前
一二三物联网:领航济南制造业数字化绿色化协同转型
人工智能·科技·物联网