OpenCV
学习之旅
话说已经有一周没见面了,勇士们每天都聚集在我的门前敲门,请求我出山,可是我仍然忙于我的AI大作业,操作系统实验和信号与系统实验,简称三座大山。看在你们的情真意切,周末抽出一点时间帮助你们解决公主布置的问题。
上回说到IU公主想要比OpenCV招胥,到底谁能成为贤胥呢?
Chapter 3
3.1 图像的基本操作
访问和修改像素值
大家应该从IU公主那里听说了,图像其实是一个矩阵,我们可以通过行和列来访问像素值,对于一个BGR图像,它返回一个由蓝色、绿色和红色组成的数组。对于灰度图像,只返回相应的灰度。
img = cv.imread('iu.jpg')
px = img[100, 100]
print(px)
# 仅访问红色像素
red = img[100, 100, 2]
print(red)
上述代码输出结果为:
[182 158 160]
160
我们也可以对某个位置的像素进行修改:
img[100, 100] = [255, 255, 255]
访问图像属性
图像属性包括行数,列数和通道数,图像数据类型,像素数等。
图像的形状可通过img.shape
来访问,它将返回行,列和通道数的元组。
print(img.shape)
# 程序输出结果(827, 828, 3)
这个结果表明图像827行,828列,3通道,如果图像是灰度,则只会有两个数据,无灰度。这个函数可以用来区别一个图片是彩色还是灰度。
print(img.size)
# 2054268
img.size
函数用来访问像素总数,img.dtype
函数用来访问图像的数据类型。
print(img.dtype)
# uint8
图像感兴趣区域ROI
对于某位勇士的照片,IU公主往往只关注这位勇士的脸庞是否英骏,所以她需要一位掌握选择人脸区域的勇士作为配偶。
ball = img[280:340, 330:390]
img[273:333, 100:160] = ball
cv.imshow('messi', img)
cv.waitKey(0)
cv.destroyAllWindows()
这位勇士将梅西脚下的球从一个变成两个,IU公主听了直摇头(dogshake.jpg)。
拆分和合并图像通道
我们有时候可能要单独处理图像的R,G,B通道,所以我们将BGR图像拆分为单个通道,并将这些单独的频道加入BGR图片,代码如下:
b, g, r = cv.split(img)
img = cv.merge((b, g, r))
b = img[:, :, 0]
# 将所有红色像素置0,无需拆分通道
img[:, :, 2] = 0
由于IU公主是个大忙人,所以IU公主对各项时间性能指标都非常看重,像split
这种操作非常耗时,IU公主告诉你要谨慎使用~
为图像设置边框(填充)
IU公主是个讲究的主,所以她希望和她有关的照片都被装裱~
想要在图像周围创建边框,可以使用cv.copyMakeBorder()
函数,它在卷积运算和零填充等方面有更多的应用。该函数的参数如下:
src:输入图像。
top,bottom,left,right:边界宽度
borderType:边框的类型,如下:
cv.BORDER_CONSTANT:添加恒定的彩色边框,该值应作为下一个参数给出。
cv.BORDER_REFLECT:边框将是边框元素的镜像。fedcba|abcdefgh|hgfedcba
value:边框颜色,如果边框类型为cv.BORDER_CONSTANT
示例代码如下:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255,0,0]
img1 = cv.imread('opencv-logo.jpg')
replicate = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT)
reflect101 = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_WRAP)
constant= cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_CONSTANT,value=BLUE)
plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')
plt.show()
3.2 图像上的算数运算
在神光圣殿,女王IU有自己一套治国理政的方略~
图像加法
图像的基本运算包括加法,减法,按位运算等。其中OpenCV的加法与numpy的加法有区别,OpenCV的加法是饱和运算,而Numpy加法是模运算。我们可以使用OpenCV函数cv.add()
或者通过numpy操作res=img1+img2
添加两个图像。需要注意的是,两个图像应具有相同的深度和类型,或者第二个图像可以只是一个标量值。
import numpy as np
import cv2 as cv
x = np.uint8([250])
y = np.uint8([10])
print(cv.add(x, y))
print(x+y)
如下面这段程序,输出结果如下:
[[255]]
[4]
图像融合
IU女王十分喜欢OpenCV,想和OpenCV的logo融为一体,所以她给勇士们的第2道题是把IU女王的照片和OpenCV的logo融合~,身为勇士的你,既想博得女王的芳心,又想抢先其他勇士,于是你找到了我。
图像融合也是图像的加法,但是对图像赋予了不同的权重,效果是有融合或透明的感觉。公式如下:
$$
G(X)=(1-\alpha)f_0(x)+\alpha f_1(x)
$$
你学会了这个公式之后,你开始动手融合图像,使用cv.addWeighted()
函数,代码如下:
iu = cv.imread('iu.jpg')
opencv = cv.imread('opencv-logo.jpg')
# 调整图像大小(如果需要)
opencv = cv.resize(opencv, (iu.shape[1], iu.shape[0]))
dst = cv.addWeighted(iu, 0.7, opencv, 0.3, 0)
# 显示融合后的图像
cv.imshow('Blended Image', dst)
cv.waitKey(0)
cv.destroyAllWindows()
按位运算
包括AND,OR,NOT,XOR等操作,我们学习如何改变一个图像的特定区域。我想把opencv的标志放在一个图像上面。如果我添加两个图像,它会改变颜色,如果我混合它,我得到一个透明的效果。但我不希望它是透明的,如果是一个矩形区域,我可以使用ROI,但是opencv的logo不是矩形,所以我们可以用按位操作实现。
习题
使用 cv.addWeighted 函数在文件夹中创建图像的幻灯片放映,并在图像之间进行平滑过渡。
# 读取图像文件
img1 = cv.imread('iu.jpg')
img2 = cv.imread('messi5.jpg')
# 确保两个图像具有相同的尺寸
img1 = cv.resize(img1, (800, 600))
img2 = cv.resize(img2, (800, 600))
# 定义过渡参数
alpha = 0.0
# 创建窗口用于显示结果
cv.namedWindow('Slideshow', cv.WINDOW_NORMAL)
# 循环进行过渡和显示图像
while True:
# 根据过渡参数计算混合图像
blend = cv.addWeighted(img1, 1 - alpha, img2, alpha, 0)
# 显示混合图像
cv.imshow('Slideshow', blend)
# 通过修改过渡参数实现平滑过渡
alpha += 0.01
# 如果过渡参数超过范围,退出循环
if alpha >= 1.0:
break
# 等待一段时间,控制过渡速度
cv.waitKey(50)
# 等待按键关闭窗口
cv.waitKey(0)
cv.destroyAllWindows()
3.3 性能衡量和提升技术
征服iu女王,需要有最快的速度~
使用OpenCV衡量性能
cv.getTickCount
函数返回从参考时间到调用函数那一刻之间的时钟周期数。因此,如果在函数执行之前和之后调用它,则会获得用于执行函数的时间周期数。
cv.getTickFrequency
函数返回时钟周期频率或每秒的时钟周期数。因此,要找到执行时间(以秒位单位),代码如下:
import cv2 as cv
e1 = cv.getTickCount()
# 你的执行代码
e2 = cv.getTickCount()
time = (e2 - e1) / cv.getTickFrequency()
print(time)
# 2.9e-06
用中位数滤波,测试时间:
import cv2 as cv
img1 = cv.imread('messi5.jpg')
e1 = cv.getTickCount()
for i in range(5,49,2):
img1 = cv.medianBlur(img1, i)
e2 = cv.getTickCount()
t = (e2 - e1)/cv.getTickFrequency()
print(t)
# 0.289932
OpenCV中的默认优化
许多OpenCV函数都是使用SSE2,AVX等进行优化的。它还包含未优化的代码。我们可以使用cvUseoptimized
检查是否启用/禁用和cvSetuseoptimized
以启用/禁用它。
在IPython中衡量性能
IPython提供了一个神奇的命令计时器来执行操作。且OpenCV的速度远比numpy的快。
Chapter 4
iu女王希望神光圣殿是光芒万丈的,而不是一片死灰~
4.1 改变颜色空间
改变颜色空间
我们重点研究两个最广泛使用的BGR->灰色和BGR->HSV。对于颜色转换,我们使用cv函数,cvtColor(input_image,flag),其中flag决定转换类型。BGR->灰度转换,我们使用标志cv.COLOR_BGR2GRAY
。类似地,对于BGR->HSV,我们使用标志cv.COLOR_BGR2HSV。
OpenCV中HSV颜色模型及颜色分量范围 - 知乎 (zhihu.com)
对象追踪
HSV的色相范围为[0,179],饱和度范围为[0,255],值范围为[0,255]。不同的软件使用不同的规模。因此,如果要将OpenCV值和它们比较,需要将这些范围标准化。
我们准备提取一个有颜色的对象,在HSV中比在BGR颜色空间中更容易表示颜色,
import cv2 as cv
import numpy as np
img = cv.imread('ball.png')
img = cv.resize(img, (640, 480))
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
# 定义HSV中蓝色的范围
lower_yellow = np.array([26, 43, 46])
upper_yellow = np.array([34, 255, 255])
# 设置HSV的阈值使得只取蓝色
mask = cv.inRange(hsv, lower_yellow, upper_yellow)
# 将掩膜和图像逐像素叠加
res = cv.bitwise_and(img, img, mask=mask)
cv.imshow('src', img)
cv.imshow('mask', mask)
cv.imshow('res', res)
cv.waitKey(0)
cv.destroyAllWindows()
如何找到要追踪的HSV值?
如果想要查找绿色的HSV值,代码如下:
hsv_green = cv.cvtColor(green,cv.COLOR_BGR2HSV)
习题
iu女王可不单单喜欢某种颜色,她同时喜欢了多种颜色,所以请你提取出opencv-logo中的红,绿,蓝三种颜色的图形。
import cv2 as cv
import numpy as np
img = cv.imread('opencv-logo.jpg')
img = cv.resize(img, (640, 480))
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
# 定义HSV中蓝色的范围
lower_red = np.array([0, 43, 46])
upper_red = np.array([10, 255, 255])
lower_green = np.array([35, 43, 46])
upper_green = np.array([77, 255, 255])
lower_blue = np.array([100, 43, 46])
upper_blue = np.array([124, 255, 255])
# 设置HSV的阈值使得只取蓝色
mask_red = cv.inRange(hsv, lower_red, upper_red)
mask_green = cv.inRange(hsv, lower_green, upper_green)
mask_blue = cv.inRange(hsv, lower_blue, upper_blue)
mask_combined = cv.bitwise_or(mask_red, cv.bitwise_or(mask_green, mask_blue))
# 将掩膜和图像逐像素叠加
res = cv.bitwise_and(img, img, mask=mask_combined)
cv.imshow('src', img)
cv.imshow('res', res)
cv.waitKey(0)
cv.destroyAllWindows()
4.2 图像的几何变换
iu女王对几何学相当感兴趣,致力于研究OpenCV上的平移,旋转,仿射变换等。
变换
OpenCV提供了两个转换函数cv.warpAffine
和cv.warpPerspective
。其中cv.warpAffine
采用$23$转换矩阵,而cv.warpPerspective
采用$33$转换矩阵作为输入。
缩放
缩放主要是调整图像的大小,OpenCV有一个resize
函数,其实这个函数我们在之前也见过,就是要做图像加法的时候,由于两个图像的大小不一样,我们用resize
函数进行调整。
图像的大小可以手动指定,也可以指定放缩比例。或者使用不同的插值方法,首选的插值方法是cv.INTER_AREA
用于缩小,cv.INTER_CUBIC
和cv.INTER_LINEAR
用于缩放。默认情况下,出于所有调整大小的目的,使用的插值方法为cv.INTER_LINEAR
。
import cv2 as cv
import numpy as np
img = cv.imread('messi5.jpg')
cv.imshow('src', img)
# fx和fy是缩放因子
img = cv.resize(img, None, fx=1, fy=1, interpolation=cv.INTER_CUBIC)
# 或者
height, width = img.shape[:2]
res = cv.resize(img, (2*width, 2*height), interpolation=cv.INTER_CUBIC)
cv.imshow('dst', img)
cv.imshow('res', res)
cv.waitKey(0)
cv.destroyAllWindows()
平移
平移是物体位置的移动,假设在(x,y)方向上的位移是(tx,ty),可以通过转换矩阵$\mathbf{M}$,如下所示:
$$
\begin{matrix}
1&0&t_x\
0&1&t_y\
\end{matrix}
$$
我们可以把这个矩阵放入np.float32
类型的numpy数组中,并传递给cv.warpAffine
函数。
旋转
图像旋转角度$\theta$是通过以下形式的变换矩阵实现的:
$$
\begin{matrix}
cos\theta&-sin\theta\
sin\theta&cos\theta\
\end{matrix}
$$
但是OpenCV提供了可缩放的旋转以及可调整的旋转中心,因此,我们可以在自己选定的任何位置旋转,修改后的变换矩阵为:
$$
\begin{matrix}
\alpha&\beta&(1-\alpha)center.x-\betacentery\
-\beta&\alpha&\betacenter.x+(1-\alpha)center.y
\end{matrix}
$$
其中:
$$
\alpha=scalecos\theta\
\beta=scale*sin\theta
$$
为了找到这个转换矩阵,OpenCV提供了一个函数cv.getRotationMatrix2D
。示例代码如下:
import cv2 as cv
import numpy as np
img = cv.imread('messi5.jpg', 0)
rows, cols = img.shape
# 平移
# M = np.float32([[1, 0, 100], [0, 1, 50]])
# 旋转 center + angle + scale
M = cv.getRotationMatrix2D(((cols-1)/2.0, (rows-1)/2.0), 90, 1)
dst = cv.warpAffine(img, M, (cols, rows))
cv.imshow('dst', dst)
cv.waitKey(0)
cv.destroyAllWindows()
仿射变换
在仿射变换中,原始图像中的所有平行线在输出图像仍然平行。为了找到变换矩阵,我们需要输入图像中的三个点及其在输出图像中的对应位置。然后cv.getAffineTransform
将创建一个2*3矩阵,该矩阵将传递给cv.warpAffine
。
img = cv.imread('drawing.png')
rows,cols,ch = img.shape
pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[10,100],[200,50],[100,250]])
M = cv.getAffineTransform(pts1,pts2)
dst = cv.warpAffine(img,M,(cols,rows))
plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
透视变换
对于透视变换,我们需要$33$变换矩阵。即使在转换后,直线也将保持直线。要找到此变换矩阵,我需要在输入图像上有4个点,在输出图像上需要相应的点。在这四个点中,其中三个不应共线。然后可以通过函数cv.getPerspectiveTransform
找到变换矩阵。然后将cv.warpPerspective
应用于此$33$转换矩阵。
import matplotlib.pyplot as plt
import numpy as np
img = cv.imread('sudoku.jpg')
rows, cols, ch = img.shape
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv.getPerspectiveTransform(pts1, pts2)
dst = cv.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()
4.3 图像阈值
简单阈值
对于每个像素,应用相同的阈值。如果像素值小于阈值,则将其设置为0,否则设置为最大值。函数cv.threshold
用于应用阈值。第一个参数是源图像,它应该是灰度图像。第二个参数是阈值,用于对像素值进行分类。第三个参数是分配给超过阈值的像素值的最大值。OpenCV提供了不同类型的阈值,这由函数的第四个参数给出。通过使用cv.THRESH_BINARY
类型,所有简单的阈值类型有:cv.THRESH_BINARY
,cv.THRESH_BINARY_INV
,cv.THRESH_TRUNC
,cv.THRESH_TOZERO
,cv.THRESH_TOZERO_INV
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img = cv.imread('gradient.png', 0)
ret1, thresh1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
ret2, thresh2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV)
ret3, thresh3 = cv.threshold(img, 127, 255, cv.THRESH_TRUNC)
ret4, thresh4 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO)
ret5, thresh5 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV)
titles = ['Original Image', '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], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
自适应阈值
简单阈值我们是使用一个全局值作为阈值,但是如果图像在不同区域具有不同的光照条件,这种情况下,自适应阈值阈值化可以提供帮助。在此,算法基于像素周围的小区确定像素的阈值。因此,对于同一图像的不同区域,我们获得不同的阈值,这为光照度变化的图像提供了更好的结果。
除上述参数外,方法cv.adaptiveThreshold
还包括三个输入参数:
该adaptiveMethod决定阈值是如何计算的:
cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻近区域的平均值减去常数C
cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域值的高斯加权总和减去常数C
该BLOCKSIZE确定附近区域的大小,C是从邻域像素的平均或加权总和中减去的一个常数C
调用格式:
void adaptiveThreshold( InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholdType, int blockSize, double C );
src
表示需要进行二值化的图像;需要注意的是,该输入必须是8-bit单通道的图像;dst
表示输出图像的二值图像;maxValue
是一个非零值,用于对哪些满足条件的阈值进行赋值;adaptiveMethod
表示选择哪一种自适应阈值算法;Opencv提供两种,ADAPTIVE_THRESH_MEAN_C
与ADAPTIVE_THRESH_GAUSSIAN_C
,下面会详细介绍;thresholdType
表示二值化类型,OpenCV提供两种,THRESH_BINARY
与THRESH_BINARY_INV
,下面会详细介绍;blocksize
表示参与计算的像素的领域范围,必须使用奇数;C
可以为正数, 零或者负数;用于在计算过程中容忍程度;
下面代码比较了光照变化的图像的全局阈值和自适应阈值:
import cv2 as cv
import numpy as np
img = cv.imread('sudoku.jpg', 0)
# C感觉有点调亮图片的作用,
thresh1 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 25, 2)
ret1, thresh2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
cv.imshow('Adaptive Mean Thresholding', thresh1)
cv.imshow('Simple Thresholding', thresh2)
cv.waitKey(0)
cv.destroyAllWindows()
Otsu的二值化
在全局阈值化中,我们使用任意选择的值作为阈值,相反,Otsu的方法避免了必须选择一个值并自动确定它。考虑仅具有两个不同图像值的图像,其中直方图仅包含两个峰,一个好的阈值应该在这两个值中间,类似的,Otsu的方法从图像直方图中确定最佳全局阈值。
因此,使用cv.threshold
作为附加标志传递。阈值可以任意选择,然后,算法找到最佳阈值,该阈值作为第一输出返回。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png',0)
# 全局阈值
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu阈值
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 高斯滤波后再采用Otsu阈值
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 绘制所有图像及其直方图
images = [img, 0, th1,
img, 0, th2,
blur, 0, th3]
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()
标签:plt,之旅,img,imshow,学习,OpenCV,图像,阈值,cv
From: https://www.cnblogs.com/chickchick/p/17455883.html