OpenCV Tutorials 01 - GUI features

874 阅读13分钟

图形特点

一、图像常规操作

  1. 读取一幅图像—— imread
  2. 使用OpenCV展示一幅图像 —— imshow
  3. 存储一幅图像—— imwrite
# 导入一般库
import cv2 as cv
import sys
import numpy as np
import matplotlib
import matplotlib.pyplot
%matplotlib inline
# 重写展示函数(附带保存功能)
def cv_show(name, img):
    cv.imshow(name, img)
    k = cv.waitKey(0)
    if k == ord('s'):
        cv.imwrite(name, img)
    elif k == ord('g'):
        cv.imwrite(name, cv.cvtColor(img, cv.COLOR_BGR2GRAY))
    cv.destroyAllWindows()
# 读取并显示
img = cv.imread('xy.png', -1)
cv_show('xy', img)

xy.PNG

# 存储为灰度图并读取显示
cv.imwrite('xy_gray.png',cv.cvtColor(img, cv.COLOR_BGR2GRAY))
gray = cv.imread('xy_gray.png')
cv_show('Gray', gray)

xy_gray.PNG

OpenCV 支持 Windows 位图(bmp)、便携式图像格式(pbm、 pgm、 ppm)和 Sun 光栅格式(sr、 ras)。在插件的帮助下(如果您自己构建了库,您需要指定使用它们,不过在我们默认提供的软件包中) ,您还可以加载图像格式,如 JPEG (JPEG,jpg,JPEG)、 JPEG 2000(在 CMake 中以 Jasper 命名的 jp2代码)、 TIFF 文件(TIFF,tif)和 PNG 文件(png)。此外,OpenEXR 也是一种可能。

img = cv.imread('lena.png', -1)
cv_show('Test.png',img)
saved = cv.imread('Test.png')
cv_show('Test',saved)

二、视频常规操作

  1. 读取、展示、保存视频
  2. 从摄像头读取视频并保存
  3. 以下两个函数 cv.VideoCapture(), cv.VideoWriter()

1. 从摄像头获取视频

OpenCV提供了一个简单的接口供我们调用摄像头。我们可以时候用这个结构来获取摄像图图像并转化为灰度图进行显示。

为了获取视频,我们要传建一个VideoCapture对象,其参数是设备序号或者名称。每个序号对应着不同的摄像设备。一般的笔记本电脑摄像接口的序号为 0,若有外接摄像头,则序号依次为 1, 2, ....。你可以逐帧的读取视频。在读取完毕之后,一定要记得释放(release)视频流。

import numpy as np
import cv2 as cv

cap = cv.VideoCapture(0)
if not cap.isOpened():
    print("Cannot open camera")
    exit()
while True:
    # Capture frame-by-frame
    ret, frame = cap.read()
    # if frame is read correctly ret is True
    if not ret:
        print("Can't receive frame (stream end?). Exiting ...")
        break
    # Our operations on the frame come here
    
    gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    
    # Display the resulting frame
    cv.imshow('frame', gray)
    if cv.waitKey(1) == ord('q'):
        break
# When everything done, release the capture
cap.release()
cv.destroyAllWindows()

若上述的操作没有打开摄像头,则可以在windows设置中找到隐私选项,并开启摄像头。

cap.read()函数会返回一个boolean值,可以根据此值来判别是否读取到了正确的帧或视频的结尾。cap有时候不能初始化为视频流,则代码会报错。在逐帧操作之前可以使用 isOpened 方法来判别cap是否初始化为视频流。

若要获取视频的属性,则可以使用 cap.get(propId), propId的取值范围为[0, 18] ,每一个序号代表了不同的属性,若视频含有对应的属性,则会返回对应属性值。与get方法对应,可以使用 cap.set(propId, value)对某些属性进行赋值。

tg = cv.VideoCapture('Tg.mp4')

# 先判别是否初始化为视频流
if not tg.isOpened():
    print('打开失败!')
    exit()

# 使用cap.get(cv.CAP_PROP_FRAME_WIDTH) 和 cap.get(cv.CAP_PROP_FRAME_HEIGHT) 获取视频的宽度和高度
tg.get(cv2.CAP_PROP_FRAME_WIDTH),tg.get(cv2.CAP_PROP_FRAME_HEIGHT)
(1920.0, 1080.0)
# 使用cap.set(cv.CAP_PROP_FRAME_WIDTH, value)设定帧宽
# 小demo
# 注意,python下的set参数没有CV_
tg.set(cv.CAP_PROP_FRAME_WIDTH,960 ) # 设置为一半
tg.set(cv.CAP_PROP_FRAME_HEIGHT,540) # 设置为一半
False
# 逐帧读取并处理
while True:
    ret, frame = tg.read()
    if ret == False:
        break
    # 显示
    # cv.imshow('Tg',frame)
    
    # 灰度显示
    gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    cv.imshow('Gray',gray)
    # 读取到Esc对应的ASCII码就退出
    if cv.waitKey(10) & 0xFF == 27:
        break
cv.destroyAllWindows()
tg.release()

2. 保存视频

保存视频并不像保存图像(cv2.imwrite)那样简单。我们要使用VideoWriter对象,我们要指定输出(保存)文件的名称,包括后缀。同时我们要指定FourCC编码(后面会提)。然后就是帧数(fps)和帧大小,最后我们要指定 isColor 参数,若其为 True,则编码器会加码为RGB图像,否则则存为灰度图。

FourCC是一个用于标识视频编码器的4字节编码,其所含的编码选择可以从下面网页查看www.fourcc.org/codecs.php (依赖平台),下面是一些性能良好的解编码器。

  1. 在 Fedora环境下: DIVX,XVID,MJPG,X264,WMV1,WMV2。(XVID 更可取。MJPG 的结果在高大小的视频。X264提供非常小的视频)
  2. 在 Windows 环境下: DIVX (更多的测试和添加)
  3. 在 OSX: MJPG (.Mp4)、 DIVX (.Avi)、 X264(.Mkv).

FourCC可以通过cv.VideoWriter_fourcc('M','J','P','G')或者cv.VideoWriter_fourcc(*'MJPG')` 来指定视频编码格式。

import numpy as np
import cv2 as cv
cap = cv.VideoCapture(0)
# Define the codec and create VideoWriter object
fourcc = cv.VideoWriter_fourcc(*'XVID')
out = cv.VideoWriter('output.avi', fourcc, 20.0, (640,  480))
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        print("Can't receive frame (stream end?). Exiting ...")
        break
    frame = cv.flip(frame, 0)
    # write the flipped frame
    out.write(frame)
    cv.imshow('frame', frame)
    if cv.waitKey(1) == ord('q'):
        break
# Release everything if job is finished
cap.release()
out.release()
cv.destroyAllWindows()

三、绘图函数

OpenCV内置了一些函数用于绘图:cv.line(), cv.circle() , cv.rectangle(), cv.ellipse(), cv.putText()等等,这些函数都是直接作用于图像

上面的函数都拥有一些常规的参数:

  1. img:图线的画布
  2. color:一个元组,表示了图线的颜色,例如(255, 0, 0)就是蓝色,因为是BGR编码。对于灰度图,只需传入对应的灰度值就行。彩图才能绘制彩线,若在需灰度图上绘制彩线,则应将灰度图进行叠加。
  3. thinkness:线条的宽度,如果将-1传递给封闭的图形,比如圆形,它将填充这个形状。
  4. lineType:线条样式,是否为8连接,抗锯齿线等。默认情况下,它是8连接。cv2.LINE_AA 提供抗锯齿线。

1. 绘制直线

import numpy as np
import cv2 as cv
# 创建BGR的纯黑画布
img = np.zeros((512,512,3), np.uint8)
# 第二三个参数分别为起点和终点
cv.line(img,(0,0),(511,511),(255,0,0),5)
array([[[255,   0,   0],
        [255,   0,   0],
        [255,   0,   0],
        ...,
        [  0,   0,   0],
        [  0,   0,   0],
        [  0,   0,   0]],

       [[255,   0,   0],
        [255,   0,   0],
        [255,   0,   0],
        ...,
        [  0,   0,   0],
        [  0,   0,   0],
        [  0,   0,   0]],

       [[255,   0,   0],
        [255,   0,   0],
        [255,   0,   0],
        ...,
        [  0,   0,   0],
        [  0,   0,   0],
        [  0,   0,   0]],

       ...,

       [[  0,   0,   0],
        [  0,   0,   0],
        [  0,   0,   0],
        ...,
        [255,   0,   0],
        [255,   0,   0],
        [255,   0,   0]],

       [[  0,   0,   0],
        [  0,   0,   0],
        [  0,   0,   0],
        ...,
        [255,   0,   0],
        [255,   0,   0],
        [255,   0,   0]],

       [[  0,   0,   0],
        [  0,   0,   0],
        [  0,   0,   0],
        ...,
        [255,   0,   0],
        [255,   0,   0],
        [255,   0,   0]]], dtype=uint8)
cv_show('Test_line', img)

Test_line.PNG

2. 绘制矩形

img = np.zeros((512,512,3), np.uint8)

cv.rectangle(img,(384,0),(510,128),(0,255,0),3)
cv_show('Test_Rec', img)

TestRec.PNG

3. 绘制圆

img = np.zeros((512,512,3), np.uint8)
w, h = img.shape[:2]
# 圆心只能为整数,而shape返回的是float类型,所以要使用//地板除,向下取整
# 最后一个 thinkness 参数 为 -1 则会补齐整个round
cv.circle(img,(w//2,h//2), w//2, (0,0,255), -1)
cv_show('Test_Round', img)

Test_Round.PNG

4. 绘制椭圆

要绘制椭圆,我们需要传递几个参数。一个参数是中心位置(x,y)。下一个参数是轴长度(主轴长度,次轴长度)。角是椭圆逆时针方向的旋转角。起始角和终止角表示椭圆弧的起始和终止顺时针方向,以长轴为单位测量。例如,赋值0和360给出了完整的椭圆。有关详细信息,请查看 cv.ellipse()的文档 docs.opencv.org/4.x/d6/d6e/… 。下面的示例在图像的中心绘制一个半椭圆。

# 参数依次为:画布、椭圆原点、
cv.ellipse(img,(256,256),(100,50),0,0,180,255,-1)
cv_show('Test_Ell', img)

Test_Ell.PNG

5. 绘制多边形

要绘制多边形,首先需要顶点的坐标。将这些点构成一个形状为 rowsx1x2的数组,其中的行是顶点数,它应该是 int32类型。在这里我们绘制一个小多边形的四个顶点在黄色的颜色。

img = np.zeros((512,512,3), np.uint8)
# 定义 四个顶点位置
pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
# 填入 -1 是自动计算
pts = pts.reshape((-1,1,2))
cv.polylines(img,[pts],True,(0,0,255), 2)
cv_show('Test_Poly', img)

Test_Poly.png

pts
array([[[10,  5]],

       [[20, 30]],

       [[70, 20]],

       [[50, 10]]])
  • 注意:
  1. 若polylines的第三个参数是Flase,则只会在画布上绘制出对应点,而不会连接成线,形成封闭图形。
  2. cv.polylines ()可用于绘制多条线。只需创建一个需要绘制的所有行的列表,并将其传递给函数即可。所有的线条都将单独画出。这是一种比为每一行调用 cv.line ()更好、更快的绘制一组线的方法。
## 绘制多个多边形
img = np.zeros((512,512,3), np.uint8)
# 定义 四个顶点位置
pts_1 = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)

pts = pts.reshape((-1,1,2))
# 利用阵列计算方式
pts_2 = pts_1+10
# 绘制出一组曲线
cv.polylines(img,[pts_1,pts_2],True,(0,0,255), 2)
cv_show('Test_Poly', img)

6. 在画布上添加文本

要将文本放入图像中,需要指定以下内容。

  1. 需要写入的文本数据
  2. 文本开始的位置坐标,(Opencv中文本起始点为左下角)
  3. 字体类型,可以查看cv.putText的文档来了解能使用的字体类型
  4. 字体规模:定义每个字符的尺寸
  5. 常规属性,如颜色、厚度、lineType等,推荐使用 lineType = cv.LINE_AA ,抗锯齿线看起来更好。
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img,'中文可以吗?',(10,500), font, 4,(255,255,255),2,cv.LINE_AA)
cv_show('Test_Text', img)

7. 解决中文乱码

重写一个函数,并下载simsun.ttc文件转入此目录下。参考自: blog.csdn.net/hijacklei/a…

import cv2
from PIL import Image, ImageDraw, ImageFont
import numpy as np

# font=cv2.FONT_ITALIC
def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):
    if (isinstance(img, np.ndarray)):  # 判断是否OpenCV图片类型
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    # 创建一个可以在给定图像上绘图的对象
    draw = ImageDraw.Draw(img)
    # 字体的格式
    fontStyle = ImageFont.truetype(
        "simsun.ttc", textSize, encoding="utf-8")
    # 绘制文本
    draw.text(position, text, textColor, font=fontStyle)
    # 转换回OpenCV格式
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

cap = cv2.VideoCapture('Tg.mp4')

while(1):
    ret,frame = cap.read()
    # 展示图片
    frame=cv2AddChineseText(frame,"中文可以了!!", (900, 50),(255, 0, 0), 30)
    frame = cv2.Canny(frame, 10, 50)
    cv2.imshow('capture',frame)
    if cv2.waitKey(10) & 0xFF == 27:
        break
#释放对象和销毁窗口
cap.release()
cv2.destroyAllWindows()

OutText.PNG

四、鼠标作为画笔

OpenCV内置了 cv2.setMouseCallBack()函数来绑定窗口、处理鼠标事件。

1. 示例

我们可以创建一个简单的应用:当我们双击图像就会绘制一个圆。

首先我们可以创建一个鼠标回调函数,用于处理鼠标事件。鼠标事件包括了:鼠标左键点击、鼠标右键点击,鼠标左右键双击等。它给出了每个鼠标事件的坐标(x,y)。有了这个事件和坐标,我们可以做任何我们想做的事情。要列出所有可用的事件,请在 Python 终端中运行以下代码:

import cv2 as cv
events = [i for i in dir(cv) if 'EVENT' in i]
print( events )
['EVENT_FLAG_ALTKEY', 'EVENT_FLAG_CTRLKEY', 'EVENT_FLAG_LBUTTON', 'EVENT_FLAG_MBUTTON', 'EVENT_FLAG_RBUTTON', 'EVENT_FLAG_SHIFTKEY', 'EVENT_LBUTTONDBLCLK', 'EVENT_LBUTTONDOWN', 'EVENT_LBUTTONUP', 'EVENT_MBUTTONDBLCLK', 'EVENT_MBUTTONDOWN', 'EVENT_MBUTTONUP', 'EVENT_MOUSEHWHEEL', 'EVENT_MOUSEMOVE', 'EVENT_MOUSEWHEEL', 'EVENT_RBUTTONDBLCLK', 'EVENT_RBUTTONDOWN', 'EVENT_RBUTTONUP']

创建鼠标回调函数有一个特定的格式,这个格式在任何地方都是相同的。它的不同之处仅仅在于功能的不同。所以我们的鼠标回调函数只做了一件事:在我们双击的地方画一个圆圈。所以请看下面的代码:

import numpy as np
import cv2 as cv
# 定义鼠标回调函数用于绘制圆形
def draw_circle(event,x,y,flags,param):
    if event == cv.EVENT_LBUTTONDBLCLK:
        cv.circle(img,(x,y),100,(255,0,0),-1)
        
# 创建一副画布,并绑定窗口事件
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()

双击显示绘制圆.PNG

2. 多事件示例

现在我们去寻找一个更好的应用。在这里,我们通过拖动鼠标来绘制矩形或圆形(取决于我们选择的模式) ,就像我们在 Paint 应用程序中所做的那样。所以我们的鼠标回调函数有两部分,一部分用于绘制矩形,另一部分用于绘制圆形。这个具体的例子对于创建和理解一些交互式应用程序非常有帮助,比如目标跟踪、图像分割等等

import numpy as np
import cv2 as cv
drawing = False # true if mouse is pressed
mode = True # if True, draw rectangle. Press 'm' to toggle to curve
ix,iy = -1,-1
# mouse callback function
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,0,0),-1)
                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)

接下来就要绑定鼠标事件到窗口了。在主循环里面,我们需要设置一个键盘监听事件,若按下‘m’键则切换绘制的图形

img = np.zeros((512,512,3), np.uint8)
cv.namedWindow('image')
cv.setMouseCallback('image',draw_circle)
while(1):
    cv.imshow('image',img)
    k = cv.waitKey(1) & 0xFF
    if k == ord('m'):
        mode = not mode
    elif k == 27:
        break
cv.destroyAllWindows()

五、使用滑轨栏作为调色板

OpenCV内置了 cv2.setTrackBarPos()函数来绑定窗口和滑轨栏。

1. 示例

下面在这个例子会创建一个你可以自定义颜色的应用:这个应用显示颜色和三个滑轨栏(分别决定了BGR的灰度值)。滑动滑轨栏会修改对应的通道颜色强度。颜色默认被设置为黑色。

对于 cv.createTrackbar ()函数,第一个参数是 trackbar 名称,第二个参数是它所附加的窗口名称,第三个参数是默认值,第四个参数是最大值,第五个参数是回调函数,每当 trackbar 值发生变化时执行该函数。回调函数总是有一个默认参数,即 trackbar 位置。在我们的例子中,函数什么也不做,所以我们只是传递。

滑轨栏的另一个重要应用是用作按钮或开关。在默认情况下,OpenCV 没有按钮功能。所以你可以使用 trackbar 来获得这样的功能。在我们的应用程序中,我们创建了一个开关,在这个开关中,应用程序只有在开关为 ON 时才能工作,否则屏幕总是黑色的

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)
# create switch for ON/OFF functionality
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[0 - 2] 分别是 BGR三个通道的灰度阵列
        img[:] = [b,g,r]

r
0

2. 小结:创建一个可以修改画笔颜色和画笔半径的小应用

import numpy as np
import cv2 as cv

# 初始化画布并添加滑轨组件
def nothing(x):
    pass
# 创建一个纯黑画布
img = np.zeros((560,1024,3), np.uint8)
img[:] = [255,255,255]
cv.namedWindow('image')
# 分别在窗口中添加三个通道的滑轨,和一个开关滑轨
cv.createTrackbar('R','image',0,255,nothing)
cv.createTrackbar('G','image',0,255,nothing)
cv.createTrackbar('B','image',0,255,nothing)
# create switch for ON/OFF functionality
cv.createTrackbar('thickness', 'image',1,100,nothing)
# 定义画笔绘制函数
drawing = False # true if mouse is pressed
ix,iy = -1,-1
color = (255, 255, 255)
thick = 1
# mouse callback function
def draw_circle(event,x,y,flags,param):
    # 声明使用全局变量
    global ix,iy,drawing,color,thick
    # 获取到鼠标左键按下事件,更改绘图标识和绘图起点
    if event == cv.EVENT_LBUTTONDOWN:
        drawing = True
        ix,iy = x,y
    # 获取到鼠标移动事件
    elif event == cv.EVENT_MOUSEMOVE:
        if drawing == True:
            cv.circle(img,(x,y),thick//2,color,-1)
    # 获取到鼠标左键抬起事件
    elif event == cv.EVENT_LBUTTONUP:
        drawing = False
        cv.circle(img,(x,y),thick//2,color,-1)
    # 按下鼠标中键重置画板
    elif event == cv.EVENT_MBUTTONDOWN:
        img[:] = [255, 255, 255]
    
cv.setMouseCallback('image',draw_circle)
# 死循环读取事件并处理
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')
    thick = cv.getTrackbarPos('thickness','image')
    color = (r,g,b)
    
cv.destroyAllWindows()

多功能画板.PNG