OpenCV基本操作


OpenCV本质上还是C/C++架构,只不过我们可以通过Python的接口来调用底层代码。其中的模块大致如下:

image-20230321173939350

image-20230321174122542

image-20230321174217314

3.1 图像输入与输出

处理图像之前,需要将图像文件从磁盘上读取到内存,处理完毕后再保存到内存。imgproc模块提供了这些全局函数,用来读取或保存图像文件。

3.1.1 读取图像

函数imread用于读取图像文件或加载图像文件。其声明如下:

1
cv.imread(filename[, flags])->retval

这个flags参数表示读取模式,输入是一组枚举数据,取值如下:

  • cv.IMREAD_ANYDEPTH:2,若输入的图像深度是16或者32,就返回对应深度的图像,否则转换为8位图像返回。
  • cv.IMREAD_COLOR:1,将输入图像转化为三通道彩色图像
  • cv.IMREAD_GRAYSCALE:0,将图像转为一通道灰度图
  • cv.IMREAD_UNCHANGED:-1,原封不动读取原图

如果这个函数没有读取成功,则会返回None。值得注意的是,读取的结果是一个Ndarray数组,可以通过Numpy的方法对其进行处理。

除却imread外,我们还可以通过imdecode从缓存中读取图像。

1
cv.imdecode(np.fromfile(imgpath,dtype=np.unint8),flag)

3.2.2 保存图像

我们现在用函数imwrite来保存图像。

1
imwrite(filename,img[.params])->retval

好了,我们直接来看一个例子吧,读取并保存图像

1
2
3
4
5
6
7
# 读取一张图像
img=cv.imread("./Files/img1.png",cv.IMREAD_GRAYSCALE)
# 在namewindow(GUI)显示图像
cv.imshow("window1",img)
cv.waitKey(0)
cv.imwrite("./Files/img4.jpg",img)
cv.destroyWindow("window1")

当然除了通过namewindow显示图像外,我们也可以调用matplotlib的接口:

1
2
3
4
import matplotlib.pyplot as plt
img=cv.imread("./Files/img1.png",cv.IMREAD_GRAYSCALE)
plt.imshow(img)
plt.show()

如果我们手上有两幅图像,可以通过线性加和的方式将其混合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 图像混合
alpha=0.5
img1=cv.imread("./Files/img2.png")
img2=cv.imdecode(np.fromfile("./Files/img3.png",dtype=np.uint8),-1)
if img1 is None:
sys.exit("Could not read the image1")
if img2 is None:
sys.exit("Could not read the image2")
img2=cv.resize(img2,(img1.shape[1],img1.shape[0]),interpolation=cv.INTER_LINEAR)
i1,i2,i3=img2[:,:,0],img2[:,:,1],img2[:,:,2]
img2=cv.merge((i1,i2,i3))
beta=(1.0-alpha)
dst=cv.addWeighted(img1,alpha,img2,beta,0.0,0)
cv.imshow("result",dst)
cv.waitKey(0)

这段代码需要注意的是,有一个cv.resize(img,(newH,newW),method)方法,可以通过插值的方式对图像重新采样。不同通道的图像可以通过ndarray索引的方式进行拆分,并通过cv.merge()方法进行堆叠。

merge方法等价于:

1
2
dataset=[np.random.randn(4,3) for i in range(6)]
np.stack(dataset,axis=-1)

然后图像线性加和函数addWeighted(img,alpha,img,alpha,0.0,0),实际上也类似于:

1
2
3
D=img1*alpha+img2*beta
D=D.astype(int)
dst1=np.clip(D,0,255)

我们再来看一个栗子,对多幅图像进行拼接

1
2
3
4
5
6
7
8
# 多幅图像
img1=cv.resize(img1,(100,100),interpolation=cv.INTER_LINEAR)
img2=cv.resize(img2,(100,100),interpolation=cv.INTER_LINEAR)
img=np.hstack((img1,img2))
Img=np.vstack((img1,img2))
cv.imshow("01",img)
cv.waitKey(0) # 0不自动退出,否则正数代表毫秒
cv.destroyAllWindows()

这边值得注意的是,hstackvstack分别会在dim=0dim=1上操作。

如果想要调整GUI窗口大小,可以这样:

1
2
3
4
5
6
7
8
9
# 创建一个窗口并调整大小
cv.namedWindow("02",cv.WINDOW_NORMAL)
cv.resizeWindow("02",Img.shape[1],Img.shape[0])
# resizeWindow的逻辑顺序应该在imshow之前

cv.imshow("02",Img)

cv.waitKey(0)
cv.destroyAllWindows()

3.2.3 鼠标事件

这里我们要介绍回调函数(call back function),一般情况下应用程序会通过应用编程接口调用系统库的函数,但有些库函数却要求应用程序传回一个函数,这样具有极大的灵活性。

OpenCV提供了setMousecallback接口,用来预先设置好回调函数。

1
SetMouseCallback(WindowName,onMouse,param=None)->None

我们可以定义自己的回调函数,请注意,这个函数继承自event

1
def MouseCallback(event,x,y,flags,param)

event的枚举类型有:

Name Value Meaning
EVENT_MOUSEMOVE 0 滑动
EVENT_LBUTTONDOWN 1 左键点击
EVENT_RBUTTONDOWN 2 右键点击
EVENT_MBUTTONDOWN 3 中键点击
EVENT_LBUTTONUP 4 左键放开
EVENT_RBUTTONUP 5 右键放开
EVENT_MBUTTONUP 6 中键放开
EVENT_LBUTTONBLCLK 7 左键双击
EVENT_RBUTTONBLCLK 8 右键双击
EVENT_MBUTTONBLCLK 9 中键双击

flags的类型有

Name Value Meaning
EVENT_FLAG_LBUTTON 1 左键拖拽
EVENT_FLAG_RBUTTON 2 右键拖拽
EVENT_FLAG_MBUTTON 4 中键拖拽
EVENT_FLAG_CTRLKEY 8 ctr
EVENT_FLAG_SHIFTKEY 16 shift
EVENT_FLAG_ALTKEY 32 alt

现在我们来看个栗子:

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
import cv2 as cv
import numpy as np
import math
img=np.zeros((500,500))
# 定义回调函数
nx,ny=0,0
def draw_circle(event,x,y,flags,param):
global nx,ny
if event==cv.EVENT_LBUTTONDOWN:
# 绘制圆
nx,ny=x,y
if event==cv.EVENT_LBUTTONUP:
cv.circle(img,(nx,ny),int(math.sqrt((x-nx)**2+(y-ny)**2)),255,-1)

cv.namedWindow("IMG")
cv.setMouseCallback('IMG',draw_circle)

while 1:
cv.imshow('IMG',img)
n=cv.waitKey(5) # 获取键盘输入
if n==ord('q'):
break
elif n==ord('s'):
# 保存
cv.imwrite("./Files/img5.png",img)
print("Save successfully")
cv.destroyAllWindows()
image-20230321234107166

3.2.4 滑动条事件

在OpenCV中,滑动条一般是用来在视频播放帧中选择特定帧。我们一般需要给滑动条创建一个名字,并通过这个名字进行引用。

1
CreateTrackbar(trackbarName,windowName,value,count,onChange)-> None

滑动条不会挡住父窗口的图像,只会让其变大。value表示当前滑动条的位置,count表示华东的最大值。onChange也是一个回调函数。

我们现在来看滑动条的回调函数:

1
def TrackbarCallback(pos,userdata)

其中,pos表示滑动块当前位置,userdata表示可选参数。如果不需要,就把onChange设置成None就行了。

如果我们想获取滑动块的位置,可以是:

1
2
GetTrackbarPos(Name,WindowName)->retval
SetTrackbarPos(Name,WindowName,pos)->None

下面我们举个栗子来看如何通过滑动块调节亮度。

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
36
import cv2 as cv
import numpy as np
import math

alpha=0.3
beta=80

img_path=r"./Files/img2.png"
img=cv.imread(img_path)
img2=cv.imread(img_path)

def updateAlpha(x):
global alpha,img,img2
alpha=cv.getTrackbarPos('Alpha','image')
alpha*=0.01
img=np.uint8(np.clip((alpha*img2+beta),0,255))

def updateBeta(x):
global beta,img,img2
beta=cv.getTrackbarPos("Beta",'image')
img=np.uint8(np.clip((alpha*img2+beta),0,255))

# 创建窗口
cv.namedWindow("image")
cv.createTrackbar('Alpha','image',0,300,updateAlpha)
cv.createTrackbar('Beta','image',0,255,updateBeta)

# 设置默认值
cv.setTrackbarPos('Alpha','image',100)
cv.setTrackbarPos('Beta','image',10)

while 1:
cv.imshow('image',img)
if cv.waitKey(1)==ord('q'):
break
cv.destroyAllWindows()
image-20230322003836973