数字图像处理

认识

OpenCV加载的彩色图像处于BGR模式。但是Matplotlib以RGB模式显示。因此,如果使用OpenCV读取彩色图像,则Matplotlib中将无法正确显示彩色图像。有关更多详细信息,请参见练习。

读取图像

使用cv.imread()函数读取图像。图像应该在工作目录或图像的完整路径应给出。

第二个参数是一个标志,它指定了读取图像的方式。

  • cv.IMREAD_COLOR: 加载彩色图像。任何图像的透明度都会被忽视。它是默认标志。
  • cv.IMREAD_GRAYSCALE:以灰度模式加载图像
  • cv.IMREAD_UNCHANGED:加载图像,包括alpha通道

注意 除了这三个标志,你可以分别简单地传递整数1、0或-1。

请参见下面的代码:

1
2
3
4
5
import numpy as np
import cv2 as cv

#加载彩色灰度图像
img = cv.imread('messi5.jpg'0)

显示图像

使用函数**cv.imshow()**在窗口中显示图像。窗口自动适合图像尺寸。

第一个参数是窗口名称,它是一个字符串。第二个参数是我们的对象。你可以根据需要创建任意多个窗口,但可以使用不同的窗口名称。

1
2
3
cv.imshow("gezi",img)
cv.waitKey(0);##可以传入等号左边一个k值表示传入键值(如下
cv.destroyAllWindows()

cv.waitKey()是一个键盘绑定函数。其参数是以毫秒为单位的时间。该函数等待任何键盘事件指定的毫秒。如果您在这段时间内按下任何键,程序将继续运行。如果0被传递,它将无限期地等待一次敲击键。它也可以设置为检测特定的按键

写入图像

使用函数cv.imwrite()保存图像。第一个参数是文件名,第二个参数是要保存的图像。 cv.imwrite('messigray.png',img)这会将图像以PNG格式保存在工作目录中。

总结

1
2
3
4
5
6
7
8
9
10
import numpy as np
import cv2 as cv
img = cv.imread('messi5.jpg',0)
cv.imshow('image',img)
k = cv.waitKey(0)
if k == 27: # 等待ESC退出
cv.destroyAllWindows()
elif k == ord('s'): # 等待关键字,保存和退出
cv.imwrite('messigray.png',img)
cv.destroyAllWindows()

基本操作

使用Matplotlib

Matplotlib是Python的绘图库,可为你提供多种绘图方法。你将在接下来的文章中看到它们。在这里,你将学习如何使用Matplotlib显示图像。你可以使用Matplotlib缩放图像,保存图像等。

1
2
3
4
5
6
7
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('messi5.jpg',0)
plt.imshow(img, cmap = 'gray', interpolation = 'bicubic')
plt.xticks([]), plt.yticks([]) # 隐藏 x 轴和 y 轴上的刻度值
plt.show()

plt的具体使用

在任何绘图之前,我们需要一个Figure对象,可以理解成我们需要一张画板才能开始绘图。

1
2
import matplotlib.pyplot as plt
fig = plt.figure()

在拥有Figure对象之后,在作画前我们还需要轴,没有轴的话就没有绘图基准,所以需要添加Axes。也可以理解成为真正可以作画的纸。

1
2
3
4
5
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set(xlim=[0.5, 4.5], ylim=[-2, 8], title='An Example Axes',
ylabel='Y-Axis', xlabel='X-Axis')
plt.show()

对于上面的fig.add_subplot(111)就是添加Axes的,

参数的解释:

在画板的第1行第1列的第一个位置生成一个Axes对象来准备作画。也可以通过fig.add_subplot(2, 2, 1)的方式生成Axes,前面两个参数确定了面板的划分,例如 2, 2会将整个面板划分成 2 * 2 的方格,第三个参数取值范围是 [1, 2*2] 表示第几个Axes。如下面的例子:

绘制操作

在上述所有功能中,您将看到一些常见的参数,如下所示:

  • img:您要绘制形状的图像
  • color:形状的颜色。对于BGR,将其作为元组传递,例如:(255,0,0)对于蓝色。对于灰度,只需传递标量值即可。
  • 厚度:线或圆等的粗细。如果对闭合图形(如圆)传递-1 ,它将填充形状。默认厚度= 1
  • lineType:线的类型,是否为8连接线,抗锯齿线等。默认情况下,为8连接线。cv.LINE_AA给出了抗锯齿的线条,看起来非常适合曲线。

绘制直线

1
cv.line(img,start,end,color,thickness)

参数:

  • img:要绘制直线的图像
  • Start,end: 直线的起点和终点
  • color: 线条的颜色
  • Thickness: 线条宽度

绘制圆形

1
cv.circle(img,centerpoint, r, color, thickness)

参数:

  • img:要绘制圆形的图像
  • Centerpoint, r: 圆心和半径
  • color: 线条的颜色
  • Thickness: 线条宽度,为-1时生成闭合图案并填充颜色

绘制矩形

1
cv.rectangle(img,leftupper,rightdown,color,thickness)

参数:

  • img:要绘制矩形的图像
  • Leftupper, rightdown: 矩形的左上角和右下角坐标
  • color: 线条的颜色
  • Thickness: 线条宽度

向图像中添加文字

1
cv.putText(img,text,station, font, fontsize,color,thickness,cv.LINE_AA)

参数:

  • img: 图像
  • text:要写入的文本数据
  • station:文本的放置位置
  • font:字体
  • Fontsize :字体大小

效果展示

我们生成一个全黑的图像,然后在里面绘制图像并添加文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 创建一个空白的图像
img = np.zeros((512,512,3), np.uint8)
# 2 绘制图形
cv.line(img,(0,0),(511,511),(255,0,0),5)
cv.rectangle(img,(384,0),(510,128),(0,255,0),3)
cv.circle(img,(447,63), 63, (0,0,255), -1)
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img,'OpenCV',(10,500), font, 4,(255,255,255),2,cv.LINE_AA)
# 3 图像展示
plt.imshow(img[:,:,::-1])
plt.title('匹配结果'), plt.xticks([]), plt.yticks([])
plt.show()

注意:

1
2
3
4
5
zeros(): 这是NumPy库中的函数,用于创建指定形状的全零数组。

(512, 512, 3): 这个元组表示了数组的形状。在这里,(512, 512, 3)表示创建一个三维数组,其中第一个维度长度为512,第二个维度长度为512,第三个维度长度为3。这种形式通常用于表示彩色图像,其中512x512表示图像的高度和宽度,3表示图像的RGB通道。

因此,通过zeros((512, 512, 3))这行代码可以创建一个大小为512x512的全零三维数组,用于表示一个黑色的512x512像素的彩色图像。

鼠标绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import numpy as np
import cv2 as cv
drawing = False # 如果按下鼠标,则为真
mode = True # 如果为真,绘制矩形。按 m 键可以切换到曲线
ix,iy = -1,-1
# 鼠标回调函数
def draw_circle(event,x,y,flags,param):
global ix,iy,drawing,mode
if event == cv.EVENT_LBUTTONDOWN:
drawing = True
ix,iy = x,y
elif event == cv.EVENT_MOUSEMOVE:
if drawing == True:
if mode == True:
cv.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
else:
cv.circle(img,(x,y),5,(0,0,255),-1)
elif event == cv.EVENT_LBUTTONUP:
drawing = False
if mode == True:
cv.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
else:
cv.circle(img,(x,y),5,(0,0,255),-1)
elif event == cv.EVENT_RBUTTONUP:
mode=not mode;
# 创建一个黑色的图像,一个窗口,并绑定到窗口的功能
img = np.zeros((512,512,3), np.uint8)
cv.namedWindow('image')
# 鼠标回调函数
cv.setMouseCallback('image',draw_circle)
while(1):
cv.imshow('image',img)
if cv.waitKey(20) & 0xFF == 27:
break
cv.destroyAllWindows()

global 关键字用于声明在函数内部使用全局变量。

逻辑非运算符 not

轨迹栏

在这里,我们将创建一个简单的应用程序,以显示您指定的颜色。您有一个显示颜色的窗口,以及三个用于指定B、G、R颜色的跟踪栏。滑动轨迹栏,并相应地更改窗口颜色。默认情况下,初始颜色将设置为黑色。

对于cv.getTrackbarPos()函数,第一个参数是轨迹栏名称,第二个参数是它附加到的窗口名称,第三个参数是默认值,第四个参数是最大值,第五个是执行的回调函数每次跟踪栏值更改。回调函数始终具有默认参数,即轨迹栏位置。在我们的例子中,函数什么都不做,所以我们简单地通过。

轨迹栏的另一个重要应用是将其用作按钮或开关。默认情况下,OpenCV不具有按钮功能。因此,您可以使用轨迹栏获得此类功能。在我们的应用程序中,我们创建了一个开关,只有在该开关为ON的情况下,该应用程序才能在其中运行,否则屏幕始终为黑色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy as np
import cv2 as cv
def nothing(x):
pass
# 创建一个黑色的图像,一个窗口
img = np.zeros((300,512,3), np.uint8)
cv.namedWindow('image')
# 创建颜色变化的轨迹栏
cv.createTrackbar('R','image',0,255,nothing)
cv.createTrackbar('G','image',0,255,nothing)
cv.createTrackbar('B','image',0,255,nothing)
# 为 ON/OFF 功能创建开关
switch = '0 : OFF \n1 : ON'
cv.createTrackbar(switch, 'image',0,1,nothing)
while(1):
cv.imshow('image',img)
k = cv.waitKey(1) & 0xFF
if k == 27:
break
# 得到四条轨迹的当前位置
r = cv.getTrackbarPos('R','image')
g = cv.getTrackbarPos('G','image')
b = cv.getTrackbarPos('B','image'):
s = cv.getTrackbarPos(switch,'image')
if s == 0:
img[:] = 0
else:
img[:] = [b,g,r]
cv.destroyAllWindows()

img[:]是img的所有元素

获取并修改图像中的像素点

我们可以通过行和列的坐标值获取该像素点的像素值。对于BGR图像,它返回一个蓝,绿,红值的数组。对于灰度图像,仅返回相应的强度值。使用相同的方法对像素值进行修改。

1
2
3
4
5
6
7
8
9
import numpy as np
import cv2 as cv
img = cv.imread('messi5.jpg')
# 获取某个像素点的值
px = img[100,100]
# 仅获取蓝色通道的强度值
blue = img[100,100,0]
# 修改某个位置的像素值
img[100,100] = [255,255,255]

获取图像的属性

图像属性包括行数,列数和通道数,图像数据类型,像素数等。

image-20191016151042764

图像通道的拆分与合并

有时需要在B,G,R通道图像上单独工作。在这种情况下,需要将BGR图像分割为单个通道。或者在其他情况下,可能需要将这些单独的通道合并到BGR图像。你可以通过以下方式完成。

1
2
3
4
# 通道拆分
b,g,r = cv.split(img)
# 通道合并
img = cv.merge((b,g,r))

色彩空间的改变

OpenCV中有150多种颜色空间转换方法。最广泛使用的转换方法有两种,BGR↔Gray和BGR↔HSV。

API:

1
cv.cvtColor(input_image,flag)

参数:

  • input_image: 进行颜色空间转换的图像
  • flag: 转换类型
    • cv.COLOR_BGR2GRAY : BGR↔Gray
    • cv.COLOR_BGR2HSV: BGR→HSV

傅里叶变换

当我们谈论图像处理时,傅里叶变换是一项非常重要的工具。傅里叶变换可以将一个图像从空间域(时域)转换到频域,从而揭示图像中不同频率的成分。在频域中,图像可以表示为由不同频率的正弦和余弦函数组成的复杂图案。

傅里叶变换的基本思想是将一个信号(包括图像)分解为一系列不同频率的正弦和余弦波形,每个波形都有特定的振幅和相位。这样做的好处是,通过查看图像的频谱,我们可以分析图像中不同频率的成分,并且可以在频域中对图像进行各种操作,例如滤波、增强特定频率的成分等。

频域滤波是在频域中对图像进行滤波操作的过程。通过对图像的频谱进行操作,我们可以滤除或增强图像中特定频率的成分这种方法在图像去噪、边缘检测、图像增强等方面都有广泛的应用

常见的频域滤波包括:

低通滤波器:滤除高频成分,保留图像中的低频信息,常用于图像去噪
高通滤波器:滤除低频成分,保留图像中的高频信息,常用于边缘检测
带通滤波器:只保留某个频率范围内的成分,常用于图像增强和特定频率的提取。
通过在频域中对图像进行滤波操作,我们可以实现一些在空间域中难以实现的图像处理任务,同时也可以更好地理解图像的频率特性.

对于图像,使用2D离散傅里叶变换(DFT)查找频域。一种称为快速傅立叶变换(FFT)的快速算法用于DFT的计算。

numpy实现

傅里叶变换可逆,傅里叶变换是为了得到图片低频和高频做不同的处理。

numpy.fft.fft2:实现傅里叶变换,傅里叶变换返回的是一个复数数组

numpy.fft.fftshiift:将0频域移到频谱中心(低频

20*np.log(np.abs(fshift)):将复数变成灰度图

以上只是得到频谱信息。低频是细节信息,高频是边缘信息。

进行低频或者高频处理之后,进行图像逆变换(即恢复原始图像),处理的信息也会反应在原始图像上。

逆傅里叶变换

numpy实现

numpy.fft.iffshift:fftshiift的逆函数。(中心0频域又回到左上角

numpy.fft.ifft2:逆变换