程序员开发实例大全宝库

网站首页 > 编程文章 正文

OpenCV-直方图(opencv直方图规定化)

zazugpt 2024-09-04 00:24:00 编程文章 26 ℃ 0 评论

直方图

如何使用OpenCV和Numpy函数查找直方图,如何使用OpenCV和Matplotlib函数绘制直方图

cv.calcHist(),np.histogram()

什么是直方图:

您可以将直方图视为图形或绘图,从而可以总体了解图像的强度分布。它是在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。

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

01 OpenCV 中的直方图计算:

因此,现在我们使用cv.calcHist()函数查找直方图。让我们熟悉一下该函数及其参数:cv.calcHist(images,channels,mask,histSize,ranges [,hist [,accumulate]])

  1. images:它是uint8或float32类型的源图像。它应该放在方括号中,即“ [img]”。
  2. channels:也以方括号给出。它是我们计算直方图的通道的索引。例如,如果输入为灰度图像,则其值为[0]。对于彩色图像,您可以传递[0],[1]或[2]分别计算蓝色,绿色或红色通道的直方图。
  3. mask:图像掩码。为了找到完整图像的直方图,将其指定为“无”。但是,如果要查找图像特定区域的直方图,则必须为此创建一个掩码图像并将其作为掩码。
  4. histSize:这表示我们的BIN计数。需要放在方括号中。对于全尺寸,我们通过[256]。
  5. ranges:这是我们的RANGE。通常为[0,256]。

因此,让我们从示例图像开始。只需以灰度模式加载图像并找到其完整直方图即可。

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

if __name__ == '__main__':
    img = cv.imread('images/0.jpg',cv.IMREAD_GRAYSCALE)
    hist = cv.calcHist([img], [0], None, [256], [0, 256])
    # print(hist.shape) # (256, 1) # 每个值对应于该图像中具有相应像素值的像素数
    # 绘制直方图
    plt.plot(hist)
    plt.show()

02 Numpy中的直方图计算:

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

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

if __name__ == '__main__':
    img = cv.imread('images/0.jpg', cv.IMREAD_GRAYSCALE)
    # 1
    # hist = cv.calcHist([img], [0], None, [256], [0, 256])
    # # print(hist.shape) # (256, 1) # 每个值对应于该图像中具有相应像素值的像素数
    # # 绘制直方图
    # plt.plot(hist)
    # plt.show()

    # 2
    # hist, bins = np.histogram(img.ravel(), 256, [0, 256])  # img.ravel() 的作用是降维
    # # print(img.shape) # (1080, 1920)
    # # print(img.ravel().shape) # (2073600,)
    # plt.plot(hist)
    # plt.show()

    # 另外 Numpy还有另一个函数np.bincount(),它比np.histogram()快10倍左右,对于一维直方图,您可以更好地尝试一下
    hist = np.bincount(img.ravel(), minlength=256)
    plt.plot(hist)
    plt.show()

如何绘制直方图:

两种方法:

1. 简洁有力的方法:使用Matplotlib绘图功能

2. 稍长的方法:使用OpenCV绘图功能(cv.line或cv.polyline函数,具体略)

import numpy as np
import matplotlib.pyplot as plt

if __name__ == '__main__':
    img = cv.imread('images/0.jpg', 0)
    plt.hist(img.ravel(), 256, [0,256])  # Matplotlib带有直方图绘图功能:matplotlib.pyplot.hist() 它直接找到直方图并将其绘制。您无需使用calcHist()或np.histogram()函数来查找直方图
    plt.show()

掩码的使用:

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

if __name__ == '__main__':
    img = cv.imread('images/0.jpg', 0)
    # create a mask
    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])
    plt.subplot(221), plt.imshow(img, 'gray')
    plt.subplot(222), plt.imshow(mask, 'gray')
    plt.subplot(223), plt.imshow(masked_img, 'gray')
    plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
    plt.xlim([0, 256])
    plt.show()

直方图均衡:

将学习直方图均衡化的概念,并利用它来提高图像的对比度

直方图均衡化是图像处理领域中利用图像直方图对对比度进行调整的方法

概述:

这种方法通常用来增加许多图像的全局对比度,尤其是当图像的有用数据的对比度相当接近的时候。通过这种方法,亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度,直方图均衡化通过有效地扩展常用的亮度来实现这种功能。

这种方法对于背景和前景都太亮或者太暗的图像非常有用,这种方法尤其是可以带来X光图像中更好的骨骼结构显示以及曝光过度或者曝光不足照片中更好的细节。这种方法的一个主要优势是它是一个相当直观的技术并且是可逆操作,如果已知均衡化函数,那么就可以恢复原始的直方图,并且计算量也不大。这种方法的一个缺点是它对处理的数据不加选择,它可能会增加背景噪声的对比度并且降低有用信号的对比度。

均衡化前后的图像的直方图

例子:

下面的这个例子是一个8位的8×8灰度图像:

该灰度图像的灰度值出现次数如下表所示,为了简化表格,出现次数为0的值已经被省略

累积分布函数(cdf)如下所示,与上一表格类似,为了简化,累积分布函数值为0的灰度值已经被省略

代码:

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

if __name__ == '__main__':
    img = cv.imread('images/aaa.jpg', cv.IMREAD_GRAYSCALE)
    hist, bins = np.histogram(img.flatten(), 256, [0, 256])
    cdf = hist.cumsum()  # shape (256,1)
    cdf_normalized = cdf / cdf.max() * float(hist.max())

    plt.plot(cdf_normalized, color='b')
    plt.hist(img.flatten(), 256, [0, 256], color='r')
    plt.xlim([0, 256])
    plt.legend(('cdf', 'histogram'), loc='upper left')
    plt.show()

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

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

if __name__ == '__main__':
    img = cv.imread('images/aaa.jpg', cv.IMREAD_GRAYSCALE)
    equ = cv.equalizeHist(img)
    plt.subplot(1, 2, 1), plt.imshow(img, 'gray')
    plt.title("img"), plt.xticks([]), plt.yticks([])

    plt.subplot(1, 2, 2), plt.imshow(equ, 'gray')
    plt.title("equ"), plt.xticks([]), plt.yticks([])
    plt.show()

当图像的直方图限制在特定区域时,直方图均衡化效果很好。在直方图覆盖较大区域(即同时存在亮像素和暗像素)的强度变化较大的地方,效果不好。

CLAHE 自适应直方图均衡

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

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

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

if __name__ == '__main__':
    img = cv.imread('images/aaa.jpg', cv.IMREAD_GRAYSCALE)
    equ = cv.equalizeHist(img)
    clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    cl1 = clahe.apply(img)

    plt.subplot(2, 2, 1), plt.imshow(img, 'gray')
    plt.title("img"), plt.xticks([]), plt.yticks([])

    plt.subplot(2, 2, 2), plt.imshow(equ, 'gray')
    plt.title("equ"), plt.xticks([]), plt.yticks([])

    plt.subplot(2, 2, 3), plt.imshow(cl1, 'gray')
    plt.title("cl1"), plt.xticks([]), plt.yticks([])

    plt.show()

二维直方图:

学习查找和绘制2D直方图

在之前,我们计算并绘制了一维直方图。 之所以称为一维,是因为我们仅考虑一个特征,即像素的灰度强度值

但是在二维直方图中,您要考虑两个特征是每个像素的色相(色调)饱和度值。

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

if __name__ == '__main__':
    img = cv.imread('images/0.jpg')
    hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
    hist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
    # channel = [0,1],因为我们需要同时处理H和S平面
    # bins = [180,256] 对于H平面为180,对于S平面为256
    # channel = [0,1],因为我们需要同时处理H和S平面
    # range = [0,180,0,256] 色相(调)值介于0和180之间,饱和度介于0和256之间

    plt.imshow(hist, interpolation='nearest') # X轴显示S值,Y轴显示色相
    plt.show()
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

if __name__ == '__main__':
    img = cv.imread('images/0.jpg')
    hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)

    # Numpy 中的二维直方图   np.histogram2d   h               s
    hist, xbins, ybins = np.histogram2d(hsv[...,0].ravel(), hsv[...,1].ravel(), [180, 256], [[0, 180], [0, 256]])
    # 第一个参数是H平面,第二个是S平面,第三个是对于H平面为180,对于S平面为256,第四个是它们的范围。

    plt.imshow(hist, interpolation='nearest')  # X轴显示S值,Y轴显示色相
    plt.show()

直方图反投影:

傅里叶变换

使用OpenCV查找图像的傅立叶变换

利用Numpy中可用的fft (快速傅立叶变换)函数

cv.dft(),cv.idft(),

dft: 离散傅里叶变换

它是一种线性积分变换,用于信号在时域(或空域)和频域之间的变换。

而又知:

任何连续的周期信号,都可以由一组适当的正弦曲线组合而成 ---傅里叶

所以,任何连续的周期信号都是可以用频率来表示的,

相位:

不是同时开始的一组三角函数,在叠加时要体现开始的时间,

图像上的傅里叶变换

您可以将图像视为在两个方向上采样的信号。因此,在X和Y方向都进行傅立叶变换,可以得到图像的频率表示。更直观地说,对于正弦信号,如果幅度在短时间内变化如此之快,则可以说它是高频信号。如果变化缓慢,则为低频信号。您可以将相同的想法扩展到图像。图像中的振幅在哪里急剧变化?在边缘点或噪声。因此,可以说边缘和噪声是图像中的高频内容。如果幅度没有太大变化,则它是低频分量。

傅里叶变换得到低频和高频信息,低频针对的是图片的细节,高频针对的是图片的边界,我们对频率进行处理,最终会反映到逆变换后的图像上。

如何找到傅立叶变换:

Numpy进行傅立叶变换:

Numpy具有FFT软件包来执行此操作。np.fft.fft2()为我们提供了频率转换,它将是一个复杂的数组。

它的第一个参数是输入图像,即灰度图像。

第二个参数是可选的,它决定输出数组的大小。如果它大于输入图像的大小,则在计算FFT之前用零填充输入图像。如果小于输入图像,将裁切输入图像。如果未传递任何参数,则输出数组的大小将与输入的大小相同。

现在,一旦获得结果,零频率分量(DC分量)将位于左上角。如果要使其居中,则需要在两个方向上将结果都移动N2N2。只需通过函数np.fft.fftshift()即可完成。(它更容易分析)。找到频率变换后,就可以找到幅度谱。

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

if __name__ == '__main__':
    img = cv.imread('images/0.jpg',cv.IMREAD_GRAYSCALE)

    f = np.fft.fft2(img)  # 傅里叶变换 # 返回值是一个复数的数组
    # print(type(f),type(f[0,0])) # <class 'numpy.ndarray'> <class 'numpy.complex128'>
    fshift = np.fft.fftshift(f)  # 将零频率分量移动到中心位置
    magnitude_spectrum = 20 * np.log(np.abs(fshift))  # 调整值的范围
    plt.subplot(121), plt.imshow(img, cmap='gray')
    plt.title('Input Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(magnitude_spectrum, cmap='gray')
    plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
    plt.show()

看,您可以在中心看到更多白色区域,这表明低频内容更多 .

Numpy 实现逆傅里叶变换

np.fft.ifft2() , np.fft.ifftshift()


低频,高频

滤波

接收一定的频率

高通滤波器:允许高频通过,将会增强图片尖锐的细节

低通滤波器:允许低频通过 ,将会模糊图片

代码

1

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

if __name__ == '__main__':
    img = cv.imread('images/lena.jpg', cv.IMREAD_GRAYSCALE)
    row, col = img.shape
    crow, ccol = row // 2, col // 2
    f = np.fft.fft2(img)  # 傅里叶变换 # 返回值是一个复数的数组
    fshift = np.fft.fftshift(f)  # 将零频率分量移动到中心位置

    # 去掉低频,将白色的去掉
    fshift[crow - 30:crow + 30, ccol - 30: ccol + 30] = 0

    # 逆变换回  图片
    ishift = np.fft.ifftshift(fshift)
    iimg = np.fft.ifft2(ishift)
    iimg = np.abs(iimg)

    plt.subplot(121), plt.imshow(img, cmap='gray')
    plt.title('img'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(iimg, cmap='gray')
    plt.title('iimg'), plt.xticks([]), plt.yticks([])
    plt.show()

2

具体操作:

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

if __name__ == '__main__':
    img = cv.imread('images/lena.jpg', cv.IMREAD_GRAYSCALE)
    row, col = img.shape
    crow, ccol = row // 2, col // 2
    mask = np.zeros((row, col), np.uint8)
    mask[crow - 30:crow + 30, ccol - 30:ccol + 30] = 1

    f = np.fft.fft2(img)  # 傅里叶变换 # 返回值是一个复数的数组
    fshift = np.fft.fftshift(f)  # 将零频率分量移动到中心位置
    fshift = fshift * mask

    # 逆变换回  图片
    ishift = np.fft.ifftshift(fshift)
    iimg = np.fft.ifft2(ishift)
    iimg = np.abs(iimg)

    plt.subplot(121), plt.imshow(img, cmap='gray')
    plt.title('img'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(iimg, cmap='gray')
    plt.title('iimg'), plt.xticks([]), plt.yticks([])
    plt.show()

模板匹配

如何使用模板匹配在图像中查找对象,cv.matchTemplate(),cv.minMaxLoc()

理论:

模板匹配是一种用于在较大图像中搜索和查找模板图像位置的方法。

为此,OpenCV带有一个函数cv.matchTemplate()。 它只是将模板图?像滑动到输入图像上(就像在2D卷积中一样),然后在模板图像下比较模板和输入图像的拼图。

它返回一个灰度图像,其中每个像素表示该像素的邻域与模板匹配的程度。

如果输入图像的大小为(WxH),而模板图像的大小为(wxh),则输出图像的大小将为(W-w + 1,H-h + 1)。得到结果后,可以使用cv.minMaxLoc()函数查找最大/最小值在哪。将其作为矩形的左上角,并以(w,h)作为矩形的宽度和高度。该矩形是您模板的区域。

OpenCV中的模板匹配

我们在图1 中搜索车牌,

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

if __name__ == '__main__':
    img = cv.imread('images/aaa.jpg')
    imgGray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    img2 = imgGray.copy()
    template = cv.imread('images/template.jpg', cv.IMREAD_GRAYSCALE)
    h, w = template.shape
    # 列表中所有的6种比较方法
    methods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR',
               'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']
    for meth in methods:
        temp = img2.copy()
        temp2 = img.copy()
        method = eval(meth)
        # 应用模板匹配
        res = cv.matchTemplate(temp, template, method)
        min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
        # 如果方法是TM_SQDIFF或TM_SQDIFF_NORMED,则取最小值
        if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
            top_left = min_loc
        else:
            top_left = max_loc
        bottom_right = (top_left[0] + w, top_left[1] + h)

        cv.rectangle(temp, top_left, bottom_right, (255, 255, 255), 2)
        cv.rectangle(temp2, top_left, bottom_right, (0, 255, 0), 2)

        plt.subplot(221), plt.imshow(res, cmap='gray')
        plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
        plt.subplot(222), plt.imshow(temp, cmap='gray')
        plt.title('Detected Point'), plt.xticks([]), plt.yticks([])

        plt.subplot(223), plt.imshow(temp2[..., ::-1])
        plt.title('Detected Point(color)'), plt.xticks([]), plt.yticks([])
        plt.suptitle(meth)  # 设置大标题
        plt.show()

你会看到,使用cv.TM_CCORR的结果并不理想

多对象的模板匹配

在上面,我们在图像中搜索了车牌,该车牌在图像中仅出现一次。假设您正在搜索具有多次出现的对象,则cv.minMaxLoc()不会为您提供所有位置。在这种情况下,我们将使用阈值化。

因此,在此示例中,我们将使用著名游戏超级玛丽的屏幕截图,并在其中找到硬币

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

if __name__ == '__main__':
    img_rgb = cv.imread('mario.png')
    img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
    template = cv.imread('mario_coin.png', 0)
    h, w = template.shape
    res = cv.matchTemplate(img_gray, template, cv.TM_CCOEFF_NORMED)
    threshold = 0.8
    loc = np.where(res >= threshold) # 返回的是个元组,其中的元素是两个array,zip 之后就是对应的坐标了
    for pt in zip(*loc[::-1]):
        cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)
    cv.imwrite('res.png', img_rgb)


    # arr = np.array([[1,2],[3,4]])
    # print(arr)
    # ret = np.where(arr>=2)
    # for point in zip(*ret):
    #     print(point)


点击领取Qt学习资料+视频教程~「链接」

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表