Skip to content

部分II:GUI 特征

4. 图片

学习目标

  • 读入图像
  • 显示图像
  • 保存图像

函数

  • cv2.imread() 读取图像
  • cv2.imshow() 显示图像
  • cv2.imwrite() 保存图像

扩展

  • cv2.waitKey() 异步等待,读入一个键盘值
  • cv2.destroyAllWindows() 关闭所有窗体
  • cv2.namedWindow() 创建命名窗体
  • 如何使用 Matplotlib 显示图片,并解决颜色错乱的问题

4.1 读取一张图片

py
import numpy as np
import cv2

img = cv2.imread('messi5.jpg', 0)

【警告】 即使图像的路径是错的,OpenCV 也不会提醒你的,结果是 None

读取参数

  • cv2.IMREAD_COLOR = 1:读入一副彩色图像。图像的透明度会被忽略,这是默认参数
  • cv2.IMREAD_UNCHANGED = -1:读入一幅图像,并且包括图像的 alpha 通道
  • cv2.IMREAD_GRAYSCALE = 0:以灰度模式读入图像

4.2 显示图像

py
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

【建议】你也可以先创建一个窗口,之后再加载图像。这种情况下,你可以决定窗口是否可以调整大小。使用到的函数是 cv2.namedWindow(),此时默认值为 cv2.WINDOW_AUTOSIZE = 1,可以设置为 cv2.WINDOW_NORMAL = 0

py
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image', img)

4.3 保存图像

py
cv2.imwrite('messigray.png', img)

4.4 综合示例

以灰度模式打开图像,按 s 保存,ESC 退出则不保存。

py
import cv2
img = cv2.imread('messi5.jpg', 0)
cv2.imshow('image', img)
k = cv2.waitKey(0) & 0xff
if k == 27:
    cv2.destroyAllWindows()
elif k == ord('s'):
    cv2.imwrite('messigray.png', img)
    cv2.destroyAllWindows()

4.5 使用 matplotlib

py
import cv2
import matplotlib.pyplot as plt

img = cv2.imread('cv2test.png', 0)
plt.imshow(img, cmap='gray', interpolation='bicubic')
plt.xticks([])
plt.yticks([])
plt.show()

【警告】 彩色图像使用 OpenCV 加载时是 BGR 模式。但是 Matplotib 是 RGB 模式。所以彩色图像如果已经被 OpenCV 读取,那它将不会被 Matplotib 正确显示。

颜色转换参考 Stack Overflow,也可以使用 img2 = img[:,:,::-1] 转换图像颜色空间。

py
import cv2
import matplotlib.pyplot as plt

img = cv2.imread('cv2test.png')
b, g, r = cv2.split(img)
img2 = cv2.merge([r, g, b])
plt.subplot(121)

# expects distorted color(错误的颜色)
plt.imshow(img)
plt.subplot(122)

# expect true color(正确的颜色)
plt.imshow(img2)
plt.show()

# expects true color(正确的颜色)
cv2.imshow('bgr image', img)

# expects distorted color(错误的颜色)
cv2.imshow('rgb image', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

5. 视频

学习目标

  • 读取视频
  • 显示视频
  • 保存视频
  • 从摄像头读取并显示视频

函数

  • cv2.VideoCapture() 读取摄像头
  • cv2.VideoWrite() 写入视频

扩展

  • cv2.cvtColor() 转换颜色空间
  • cv2.flip() 翻转图像

5.1 捕获摄像头

cv2.VideoCapture(0) 捕获第一个摄像头,cap.read() 读取图像。

py
import cv2

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    cv2.imshow('frame', gray)
    if cv2.waitKey(1) & 0xff == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

如果可能打开失败时,使用 cap.isOpened() 判断是否打开成功。

你可以使用函数 cap.get(propId) 来获得视频的一些参数信息。这里 propId 每一个数代表视频的一个属性,见下表。

其中的一些值可以使用 cap.set(propId, value) 来修改,value 就是你想要设置成的新值。例如,可以使用 cap.get(3)cap.get(4) 来查看每一帧的宽和高。

5.2 从文件中播放视频

输入文件名,然后播放视频文件的内容

py
import cv2

video_path = input('video path:')
cap = cv2.VideoCapture(video_path)

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    # 可以翻转过来播放
    # frame = cv2.flip(frame, 0)
    cv2.imshow('frame', frame)
    if cv2.waitKey(100) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

预测模型示例,参考 https://blog.csdn.net/learn_learn_/article/details/112007757

py
import cv2

# 此处可以设定模型,然后进行预测处理
detector = ...
video_path = 'test.mp4'
cap = cv2.VideoCapture(video_path)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    results = detector.predict(frame)

cap.release()
cv2.waitKey(-1)

跳帧的预测模型,每秒预测一次

py
import cv2

# 此处可以设定模型,然后进行预测处理
detector = ...
video_path = 'test.mp4'
capture = cv2.VideoCapture(video_path)

# 视频的帧率
fps = capture.get(cv2.CAP_PROP_FPS)

# 视频的总帧数
total_frame = capture.get(cv2.CAP_PROP_FRAME_COUNT)

for i in range(int(total_frame)):
    ret = capture.grab()
    if not ret:
        print('Error grabbing frame from movie!')
        break
    if i % fps == 0:
        ret, frame = capture.retrieve()
        if ret:
            results = detector.predict(frame)
        else:
            print('Error retrieving frame from movie!')
            break
cv2.waitKey(-1)

注意:你应该确保你已经装了合适版本的 ffmpeg 或者 gstreamer

5.3 保存捕获的视频

使用 cv2.cv.FOURCC(*'MJPG') 保存,cv2.VideoWriter() 用于写入视频。

py
import cv2

cap = cv2.VideoCapture(0)

# 定义 codec 并创建 VideoWriter 对象
fourcc = cv2.cv.FOURCC(*'XVID')
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 480))

while cap.isOpened():
    ret, frame = cap.read()
    if ret == True:
        frame = cv2.flip(frame, 0)

        # 写入裁剪的帧
        out.write(frame)

        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break

# 释放内存
cap.release()
out.release()
cv2.destroyAllWindows()

编码指南:

  • X264 适合小尺寸视频
  • MJPG 适合大尺寸视频
  • XVID 适合自适应
  • DIVX 适合 Windows 系统
  • WMV1
  • WMV2

如果想查找编码的详细信息,参考 fourcc.org

6. 绘图函数

学习目标

  • 使用 OpenCV 绘制不同的几何图形

函数

  • cv2.line() 画线
  • cv2.circle() 画圈
  • cv2.rectangle() 矩形
  • cv2.ellipse() 椭圆形
  • cv2.putText() 绘制文本

扩展

  • img:你想要绘制图形的那幅图像
  • color:形状的颜色。以 RGB 为例,需要传入一个元组,例如:(255, 0, 0) 代表蓝色。对于灰度图只需要传入灰度值
  • thickness:线条的粗细。如果给一个闭合图形设置为 -1,那么这个图形就会被填充。默认值是 1
  • linetype:线条的类型,8 连接,抗锯齿等。默认情况是 8 连接。cv2.LINE_AA 为抗锯齿,这样看起来会非常平滑

语法总结

  • line(img, pt1, pt2, color, thickness=..., lineType=..., shift=...)
  • rectangle(img, pt1, pt2, color, thickness=..., lineType=..., shift=...)
  • circle(img, center, radius, color, thickness=..., lineType=..., shift=...)
  • ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness=..., lineType=..., shift=...)
  • polylines(img, pts, isClosed, color, thickness=..., lineType=..., shift=...)

6.1 画线

py
import numpy as np
import cv2

# 黑色的背景图
img = np.zeros((512, 512, 3), np.uint8)

# 蓝色的线, 5px
cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5)

winname = 'example'
cv2.namedWindow(winname)
cv2.imshow(winname, img)
cv2.waitKey(0)
cv2.destroyWindow(winname)

6.2 画矩形

py
cv2.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)

6.3 画圆

py
cv2.circle(img, (447, 63), 63, (0, 0, 255), -1)

6.4 画椭圆

py
cv2.ellipse(img, (256, 256), (100, 50), 0, 0, 180, 255, -1)

6.5 画多边形

py
pts = np.array(
    [[10, 5],
     [20, 30],
     [70, 20],
     [50, 10]],
    np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(img, [pts], True, (255, 255, 255), 1)

如果 cv2.polylines() 第三个参数是 False,我们得到的多边形是不闭合的(首尾不相连)。

注意:cv2.polylines() 可以被用来画很多条线。只需要把想要画的线放在一个列表中,将这个列表传给函数就可以了。每条线都会被独立绘制。这会比用 cv2.line() 一条一条的绘制要快一些。

6.6 在图片上添加文字

设置参数

  • 你要绘制的文字
  • 你要绘制的位置
  • 字体类型(通过查看 cv2.putText() 的文档找到支持的字体)
  • 字体的大小
  • 文字的一般属性如颜色,粗细,线条的类型等。为了更好看一点推荐使用 linetype = cv2.LINE_AA
py
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, 'OpenCV', (10, 500), font, 4, (255, 255, 255), 2)

winname = 'example'
cv2.namedWindow(winname)
cv2.imshow(winname, img)
cv2.waitKey(0)
cv2.destroyWindow(winname)

可以使用 img = cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5) 来获得新的图像。

5. 鼠标事件

学习目标

  • 学习使用 OpenCV 处理鼠标事件

函数

  • cv2.setMouseCallback() 设置鼠标回调函数

5.1 双击的地方绘制圆

查看有哪些事件受支持

py
import cv2

def print_const(prefix: str) -> None:
    prefix += '_'
    names = ('{:22} = {:5}'.format(key, val)
             for key, val in vars(cv2).items()
             if key.startswith(prefix))
    print(*names, sep='\n')

print_const('EVENT')

ESC 退出,双击的地方绘制圆,不要按 X 关闭窗口,否则会无法绘制

py
import cv2
import numpy as np

# 鼠标回调函数
def draw_circle(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDBLCLK:
        cv2.circle(img, (x, y), 100, (255, 0, 0), -1)

# 创建图像与窗口并将窗口与回调函数绑定
img = np.zeros((512, 512, 3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_circle)

while True:
    cv2.imshow('image', img)
    if cv2.waitKey(20) & 0xFF == 27:
        break

cv2.destroyAllWindows()

拖动绘制绿色矩形,按 m 切换为绘制红色圆点

py
import cv2
import numpy as np

drawing = False
mode = True

ix, iy = -1, -1


def draw_circle(event: int, x: int, y: int, flags: int,
                param) -> None:
    global ix, iy, drawing, mode
    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        ix, iy = x, y
    elif event == cv2.EVENT_MOUSEMOVE and\
            flags == cv2.EVENT_FLAG_LBUTTON:
        if drawing == True:
            if mode == True:
                cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1)
            else:
                cv2.circle(img, (x, y), 3, (0, 0, 255), -1)
    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False


img = np.zeros((512, 512, 3), np.uint8)
# assert isinstance(img, np.ndarray)
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_circle)

while True:
    cv2.imshow('image', img)
    key = cv2.waitKey(1) & 0xff
    if key == ord('m'):
        print('mode:', mode)
        mode = not mode
    elif key == 27:
        break

cv2.destroyAllWindows()

8. 调色板

学习目标

  • 把滑动条绑定到 OpenCV 窗口上

函数

  • cv2.creatTrackbar() 创建拖动条
  • cv2.getTrackbarPos() 获取拖动条位置
py
import cv2
import numpy as np

def nothing(x: object) -> None:
    ...

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

cv2.namedWindow('image')
cv2.createTrackbar('R', 'image', 0, 255, nothing)
cv2.createTrackbar('G', 'image', 0, 255, nothing)
cv2.createTrackbar('B', 'image', 0, 255, nothing)

switch = '0:OFF\n1:ON'
cv2.createTrackbar(switch, 'image', 0, 1, nothing)

while True:
    cv2.imshow('image', img)
    key = cv2.waitKey(1) & 0xff
    if key == 27:
        break

    r = cv2.getTrackbarPos('R', 'image')
    g = cv2.getTrackbarPos('G', 'image')
    b = cv2.getTrackbarPos('B', 'image')
    s = cv2.getTrackbarPos(switch, 'image')

    if s == 0:
        img[:] = 0
    else:
        img[:] = b, g, r

cv2.destroyAllWindows()

结合上一节的知识,创建一个画板,可以自选各种颜色的画笔绘画各种图形

py
import cv2
import numpy as np

def nothing(x: object) -> None:
    pass

drawing = False
mode = True
ix, iy = -1, -1

def draw_circle(event: int, x: int, y: int, flags: int,
                param) -> None:
    global ix, iy, drawing, mode
    r = cv2.getTrackbarPos('R', 'image')
    g = cv2.getTrackbarPos('G', 'image')
    b = cv2.getTrackbarPos('B', 'image')
    color = (b, g, r)

    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        ix, iy = x, y
    elif event == cv2.EVENT_MOUSEMOVE and\
            flags == cv2.EVENT_FLAG_LBUTTON:
        if drawing == True:
            if mode == True:
                cv2.rectangle(img, (ix, iy), (x, y),
                              color, -1)
            else:
                cv2.circle(img, (x, y), 3, color, -1)
    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False

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

cv2.namedWindow('image')
cv2.createTrackbar('R', 'image', 0, 255, nothing)
cv2.createTrackbar('G', 'image', 0, 255, nothing)
cv2.createTrackbar('B', 'image', 0, 255, nothing)
cv2.setMouseCallback('image', draw_circle)

while True:
    cv2.imshow('image', img)
    key = cv2.waitKey(1) & 0xff
    if key == 27:
        break
    elif key == ord('m'):
        mode = not mode

cv2.destroyAllWindows()