资讯专栏INFORMATION COLUMN

2021-09-23 opencv学习笔记(图像变换,二值化,滤波器介绍及python实现)

CloudDeveloper / 2077人阅读

摘要:但是考虑一个双峰图像简单来说,双峰图像是一个直方图有两个峰值的图像。对于非双峰图像,二值化将不准确。在第三种情况下,我使用高斯核过滤图像以去除噪声,然后应用阈值。

颜色空间

改变颜色空间(cv2.cvtColor())

在 OpenCV 中有超过 150 种颜色空间转换的方法。但我们仅需要研究两个最常使用的方法,他们是 BGR 到 GRAY,BGR 到 HSV。

我们使用 cv.cvtColor(input_image, flag)函数进行颜色转换,其中 flag 决定了转换的类型。

对于 BGR 到 Gray 转换我们令 flag 为 cv2.COLOR_BGR2GRAY。 同样,对于 BGR 到 HSV, 我们令 flag 为 cv2.COLOR_BGR2HSV

代码:

import cv2BGR = cv2.imread("C:/Users/Zhang-Lei/Desktop/snack.png")GRAY = cv2.cvtColor(BGR, cv2.COLOR_BGR2GRAY)HSV = cv2.cvtColor(BGR, cv2.COLOR_BGR2HSV)cv2.imshow("BGR", BGR)cv2.imshow("GRAY", GRAY)cv2.imshow("HSV", HSV)cv2.resizeWindow("BGR", 500, 800)cv2.resizeWindow("GRAY", 500, 800)cv2.resizeWindow("HSV", 500, 800)cv2.waitKey()cv2.destroyAllWindows()

结果:

如想得到其他 flag 值,我们只需要输入代码查看:

代码:

flags = [i for i in dir(cv2) if i.startswith("COLOR_")]print(flags)

种类过多,这里就不展示结果了。

注意: 对于 HSV, 色调(Hue)范围为 [0,179], 饱和度(Saturation)范围为 [0,255] ,明亮度(Value)为 [0,255]. 不同的软件使用不同的比例. 所以如果你想用 OpenCV 的值与别的软件的值作对比,你需要归一化这些范围。

目标追踪

现在我们知道了如何将 BGR 图片转化为 HSV 图片,我们可以使用它去提取彩色对象。HSV 比 BGR 在颜色空间上更容易表示颜色。在我们的应用中,我们会尝试提取一个蓝色的彩色对象,方法为:

  1. 提取每一视频帧。
  2. 将 BGR 转化为 HSV 颜色空间。
  3. 我们用蓝色像素的范围对该 HSV 图片做阈值。
  4. 现在提取出了蓝色对象,我们可以随意处理图片了 。

代码:

import cv2import numpy as npcap = cv2.VideoCapture(0)  # 读取摄像头影像while 1:    _, frame = cap.read()    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)  # 转为HSV图    lower_blue = np.array([110, 50, 50])  # 限定蓝色范围下限    upper_blue = np.array([130, 255, 255])  # 限定蓝色范围上限    mask = cv2.inRange(hsv, lower_blue, upper_blue)  # 限定HSV图像范围    res = cv2.bitwise_and(frame, frame, mask=mask)  # 按位与    cv2.imshow("frame", frame)    cv2.imshow("mask", mask)    cv2.imshow("res", res)    k = cv2.waitKey(5) & 0xFF  # 检测是否按下Esc 引用&0xff,    # 正是为了只取按键对应的ASCII值后8位来排除不同按键的干扰进行判断按键是什么。    if k == 27:        breakcv2.destroyAllWindows()

结果:

可以看到这本书的蓝色对象被我们提取了出来。

如何查找某个颜色的HSV值

你可以使用相同的函数:cv2.cvtColor()。 不需要输入图片,你只需要输入你需要的 BGR 值即可. 例如, 为了找到绿色的 HSV 值,

代码:

green = np.uint8([[[0, 255, 0]]])hsv_green = cv2.cvtColor(green, cv2.COLOR_BGR2HSV)print(hsv_green)

结果:

图形变换

缩放(cv2.resize())

缩放是调整图片的大小。 OpenCV 使用 cv2.resize() 函数进行调整。可以手动指定图像的大小,也可以指定比例因子。可以使用不同的插值方法。对于下采样(图像上缩小),最合适的插值方法是 cv2.INTER_AREA 对于上采样(放大),最好的方法是 cv2.INTER_CUBIC (速度较慢)和 cv2.INTER_LINEAR (速度较快)。默认情况下,所使用的插值方法都是 cv2.INTER_AREA 。你可以使用如下方法调整输入图片大小:

代码:

import cv2img = cv2.imread("C:/Users/Zhang-Lei/Desktop/snack.png")res1 = cv2.resize(img, None, fx=1/2, fy=1/2, interpolation=cv2.INTER_CUBIC) # fx和fy分别对应横纵轴图像放大倍数height, width = img.shape[:2]res2 = cv2.resize(img, (width//2, height//2), interpolation=cv2.INTER_CUBIC)  # 第二种方法cv2.imshow("img", img)cv2.imshow("res1", res1)cv2.imshow("res2", res2)cv2.waitKey()cv2.destroyAllWindows()

结果:

可以看出两种方法都将图片的大小缩小为原来大小的一半。

平移

平移变换是物体位置的移动。如果知道 (x,y) 方向的偏移量,假设为 (t_x,t_y),则可以创建如下转换矩阵 M:

您可以将变换矩阵存为 np.float32 类型的 numpy 数组,并将其作为 cv.warpAffine 的第二个参数。请参见以下转换(100,50)的示例:

代码:

import cv2import numpy as npimg = cv2.imread("C:/Users/Zhang-Lei/Desktop/snack_gray.png", cv2.IMREAD_UNCHANGED)rows, cols = img.shapeM = np.float32([[1, 0, 100], [0, 1, 50]])res = cv2.warpAffine(img, M, (cols, rows))cv2.imshow("img", img)cv2.imshow("res", res)cv2.waitKey()cv2.destroyAllWindows()

结果:

这里可以看到得到的图像相比于原图像是向右下方平移了的。

注意:cv2.warpAffine 函数的第三个参数是输出图像的大小,其形式应为(宽度、高度)。记住宽度=列数,高度=行数。

旋转

以θ角度旋转图片的转换矩阵形式为:

但 Opencv 提供了可变旋转中心的比例变换,所以你可以在任意位置旋转图片,修改后的转换矩阵为:

其中:

为了找到这个转换矩阵,opencv 提供了一个函数
cv2.getRotationMatrix2D 。
请查看下面的示例,它将图像相对于中心旋转 90 度,而不进行任何缩放。

代码:

import cv2img = cv2.imread("C:/Users/Zhang-Lei/Desktop/snack_gray.png", cv2.IMREAD_UNCHANGED)rows, cols = img.shapeM = cv2.getRotationMatrix2D(((cols-1)/2.0, (rows-1)/2.0), 90, 1)res = cv2.warpAffine(img, M, (cols, rows))cv2.imshow("img", img)cv2.imshow("res", res)cv2.waitKey()cv2.destroyAllWindows()

结果:

可见图片已经发生了逆时针90°的旋转。

仿射变换

在仿射变换中,原始图像中的所有平行线在输出图像中仍然是平行的。为了找到变换矩阵,我们需要从输入图像中取三个点及其在输出图像中的对应位置。然后 cv2.getPerspectiveTransform 将创建一个 2x3 矩阵,该矩阵将传递给cv2.warpAffine

代码:

import cv2import matplotlib.pyplot as pltimport numpy as npimg = cv2.imread("C:/Users/Zhang-Lei/Desktop/drawing.png")rows, cols, ch = img.shapepts1 = np.float32([[50, 50], [200, 50], [50, 200]])pts2 = np.float32([[10, 100], [200, 50], [100, 250]])M = cv2.getAffineTransform(pts1, pts2)dst = cv2.warpAffine(img, M, (cols, rows))plt.subplot(121), plt.imshow(img), plt.title("Input")plt.subplot(122), plt.imshow(dst), plt.title("Output")plt.show()

结果:

透视变换

对透视转换,你需要一个 3x3 变换矩阵。即使在转换之后,直线也将保持直线。要找到这个变换矩阵,需要输入图像上的 4 个点和输出图像上的相应点。在这四点中,任意三点不应该共线。然后通过 cv2.getPerspectiveTransform 找到变换矩阵。然后对这个 3x3 变换矩阵使用 cv2.warpPerspective

代码:

import cv2import matplotlib.pyplot as pltimport numpy as npimg = cv2.imread("C:/Users/Zhang-Lei/Desktop/sudoku.png")rows, cols, ch = img.shapepts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])M = cv2.getPerspectiveTransform(pts1, pts2)dst = cv2.warpPerspective(img, M, (300, 300))plt.subplot(121), plt.imshow(img), plt.title("Input")plt.subplot(122), plt.imshow(dst), plt.title("Output")plt.show()

结果:

二值化

简单阈值法

此方法是直截了当的。如果像素值大于阈值,则会被赋为一个值(可能为白色),否则会赋为另一个值(可能为黑色)。使用的函数是 cv2.threshold。第一个参数是源图像,它应该是灰度图像。第二个参数是阈值,用于对像素值进行分类。第三个参数是 maxval,它表示像素值大于(有时小于)阈值时要给定的值。opencv 提供了不同类型的阈值,由函数的第四个参数决定。不同的类型有:

  • cv.THRESH_BINARY
  • cv.THRESH_BINARY_INV
  • cv.THRESH_TRUNC
  • cv.THRESH_TOZERO
  • cv.THRESH_TOZERO_INV

获得两个输出。第一个是 retval,稍后将解释。第二个输出是我们的阈值图像。

代码:

import cv2import matplotlib.pyplot as pltimg = cv2.imread("C:/Users/Zhang-Lei/Desktop/snack_gray.png", cv2.IMREAD_UNCHANGED)ret1, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)ret2, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)ret3, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)ret4, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)ret5, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)titles = ["Original", "BINARY", "BINARY_INV", "TRUNC", "TOZERO", "TOZERO_INV"]images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]for i in range(6):    plt.subplot(2, 3, i+1)    plt.imshow(images[i], cmap="gray")    plt.title(titles[i])    plt.xticks([])    plt.yticks([])plt.show()

结果:

可以看到,其他五幅图像都以不同的方式区分像素。

自适应阈值

在前一节中,我们使用一个全局变量作为阈值。但在图像在不同区域具有不同照明条件的条件下,这可能不是很好。在这种情况下,我们采用自适应阈值。在此,算法计算图像的一个小区域的阈值。因此,我们得到了同一图像不同区域的不同阈值,对于不同光照下的图像,得到了更好的结果。

它有三个“特殊”输入参数,只有一个输出参数。

  • Adaptive Method :它决定如何计算阈值。

  • cv2.ADAPTIVE_THRESH_MEAN_C :阈值是指邻近地区的平均值。

  • cv2.ADAPTIVE_THRESH_GAUSSIAN_C :阈值是权重为高斯窗的邻域值的加权和。

  • Block Size :它决定了计算阈值的窗口区域的大小。

  • C :它只是一个常数,会从平均值或加权平均值中减去该值。

下面的代码比较了具有不同照明的图像的全局阈值和自适应阈值:

代码:

import cv2import matplotlib.pyplot as pltimg = cv2.imread("C:/Users/Zhang-Lei/Desktop/snack_gray.png", 0)img = cv2.medianBlur(img, 5)ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)thresh2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY, 11, 2)thresh3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY, 11, 2)titles = ["Original Image", "Global Thresholding (v = 127)",          "Adaptive Mean Thresholding", "Adaptive Gaussian Thresholding"]images = [img, thresh1, thresh2, thresh3]for i in range(4):    plt.subplot(2, 2, i + 1), plt.imshow(images[i], "gray")    plt.title(titles[i])    plt.xticks([]), plt.yticks([])plt.show()

结果:

Otsu二值化(俗称大津法)

在第一部分中,有一个参数 retval。当我们进行 Otsu 二值化时,它的用途就来了。那是什么?

在全局阈值化中,我们使用一个任意的阈值,对吗?那么,我们如何知道我们选择的值是好的还是不好的呢?答案是,试错法。但是考虑一个双峰图像(简单来说,双峰图像是一个直方图有两个峰值的图像)。对于那个图像,我们可以近似地取这些峰值中间的一个值作为阈值,对吗?这就是 Otsu 二值化所做的。所以简单来说,它会自动从双峰图像的图像直方图中计算出阈值。(对于非双峰图像,二值化将不准确。)

为此,我们使用了 cv2.threshold 函数,但传递了一个额外的符号 cv2.THRESH_OTSU 。对于阈值,只需传入零。然后,该算法找到最佳阈值,并作为第二个输出返回 retval。如果不使用 otsu 阈值,则 retval 与你使用的阈值相同。

查看下面的示例。输入图像是噪声图像。在第一种情况下,我应用了值为 127 的全局阈值。在第二种情况下,我直接应用 otsu 阈值。在第三种情况下,我使用 5x5 高斯核过滤图像以去除噪声,然后应用 otsu 阈值。查看噪声过滤如何改进结果。

代码:

import cv2import matplotlib.pyplot as pltimg = cv2.imread("C:/Users/Zhang-Lei/Desktop/noisy.png", 0)# 全局阈值ret1, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)# Otsu 阈值ret2, thresh2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 经过高斯滤波的 Otsu 阈值blur = cv2.GaussianBlur(img, (5, 5), 0)ret3, thresh3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 画出所有的图像和他们的直方图images = [img, 0, thresh1, img, 0, thresh2, blur, 0, thresh3]titles = ["Original Noisy Image", "Histogram", "Global Thresholding (v=127)",          "Original Noisy Image", "Histogram", "Otsu"s Thresholding",          "Gaussian filtered Image", "Histogram", "Otsu"s Thresholding"]for i in range(3):    plt.subplot(3, 3, i * 3 + 1), plt.imshow(images[i * 3], "gray")    plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([])    plt.subplot(3, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256)    plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([])    plt.subplot(3, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], "gray")    plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([])plt.show()

结果:

Otsu 二值化原理

本节演示了 otsu 二值化的 python 实现,以展示它的实际工作原理。

由于我们使用的是双峰图像,因此 Otsu 的算法试图找到一个阈值(t),该阈值将由下式计算得到的类内加权方差最小化:

其中:

它实际上找到一个 T 值,它位于两个峰值之间,使得两个类的方差最小。它可以简单地在 python 中实现,如下所示:

代码:

import cv2import numpy as npimg = cv2.imread("C:/Users/Zhang-Lei/Desktop/noisy.png", 0)blur = cv2.GaussianBlur(img, (5, 5), 0)# 找到归一化直方图还有累计分布函数hist = cv2.calcHist([blur], [0], None, [256], [0, 256])hist_norm = hist.ravel() / hist.max()Q = hist_nor           
               
                                           
                       
                 

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/121116.html

相关文章

  • OpenCV-Python计算机视觉函数

    摘要:如果不设置这个,图片只会一瞬间显示,就消失了。括号里面也可以设置显示时长。 目录 1、概述 2、OpenCV基础 读取图片 imread 调整显示窗口大小 resizeWindow 调整图像尺寸大小 resize 色彩空间进行转换 cvtColor 绘制线段 line 绘制矩形框 recta...

    levius 评论0 收藏0
  • 学习Opencv+Python之文档OCR

    摘要:类推,求坐标值的差,当差最小时为右上角,最大时为左下角对获取的四个点组成的四边形求边长,那么转换后的四个顶点为对于原始图像的高和宽,如果输入的高为,先求比例,而后即可获得原始高宽比下的宽。 ...

    tangr206 评论0 收藏0
  • iOS利用OpenCV 实现文字行区域提取的尝试

    摘要:这是坐标百度,好像没啥好研究的了,不过出于好奇还是想知道使用是如何做到把文字区域进行框选的,所以接下来我们就看看如何在上使用实现图片中的文字框选。一些探索 最近下了几个OCR的App(比如白描),发现可以选中图片中的文字行逐行转成文字,觉得很有意思(当然想用要花钱啦),想着自己研究一下实现原理,google之后,发现了两个库,一个是OpenCV,在机器视觉方面应用广泛,图像分析必备利器。另一...

    番茄西红柿 评论0 收藏0
  • iOS利用OpenCV 实现文字行区域提取的尝试

    摘要:这是坐标百度,好像没啥好研究的了,不过出于好奇还是想知道使用是如何做到把文字区域进行框选的,所以接下来我们就看看如何在上使用实现图片中的文字框选。一些探索 最近下了几个OCR的App(比如白描),发现可以选中图片中的文字行逐行转成文字,觉得很有意思(当然想用要花钱啦),想着自己研究一下实现原理,google之后,发现了两个库,一个是OpenCV,在机器视觉方面应用广泛,图像分析必备利器。另一...

    ztyzz 评论0 收藏0
  • OpenCV搞定腾讯滑块验证码

    摘要:前言废话滑块验证码破解是一直都想搞的项目,毕竟多数网站都会采用滑块验证码,于是最近在修改论文的闲暇之余把这事儿给解决了。 前言 废话滑块验证码破解是一直都想搞的项目,毕竟多数网站都会采用滑块验证码,于是最近在修改论文的闲暇之余把这事儿给解决了。要搞现在的滑块验证码绕不开图像处理,图像处理当然是首推OpenCV-Python啦!当然我的OpenCV非常菜(P.S.两天速成不敢保证代码质量...

    loostudy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<