OpenCV-Python

计算机操作底层技术

图像数据的操作

分配 释放 复制 转换

图像是视频的输入输出 I/O 文件与摄像头的输入 图像和视频文件的输出

矩阵和向量的操作以及线性代数的算法程序

矩阵积 解方程 特征值 奇异值

各种动态数据结构

列表 队列 集合 树 图

基本的数字图像处理

滤波 边缘检测 角点检测 采样与差值 色彩转换 形态操作 直方图 图像金字塔

结构分析

连接部件 轮廓处理 距离变换 各自距计算 模块匹配 Hough变换 多边形逼近 直线拟合 椭圆拟合 Delaunay三角划分

摄像头定标

发现于跟踪定标模式 定标 基本矩阵估计 齐次矩阵估计 立体对应

运动分析

光流 运动分割 跟踪

目标识别

特征法 隐马尔可夫模型:HMM

基本的 GUI

图像与视频显示 键盘和鼠标事件处理 滚动条

图像标注

线 二次曲线 多边形 画文字

OpenCV 的结构

函数接口大体分为

core :核心模块,主要包含了 OpenCV 中最基本的结构(矩阵,点线 和 形状),以及相关的基础运算和操作

imgproc :图像处理模块,包含和 图像 相关的基础功能(滤波,梯度,改变大小等),以及一些衍生的高级功能(图像分割,直方图,形态分析和边缘/直线提取等)

highgui :提供了用户界面和文件读取的基本函数,比如图像显示窗口的生成和控制,图像/视频文件的IO

video :用于视频分析的常用功能,比如光流法(Optical Flow)和目标跟踪

calib3d :三维重建,立体视觉和相机标定等的相关功能

features2d :二维特征相关的功能,主要是一些不受专利保护的,商业友好的特征点检测和匹配等功能,比如ORB特征

object :目标检测模块,包含级联分类和Latent SVM

ml :机器学习算法模块,包含一些视觉中最常用的传统机器学习算法

flann :最近邻算法库,Fast Library for Approximate Nearest Neighbors,用于在多维空间进行聚类和检索,经常和关键点匹配搭配使用

gpu :包含了一些gpu加速的接口,底层的加速是CUDA实现

photo :计算摄像学(Computational Photography)相关的接口,当然这只是个名字,其实只有图像修复和降噪而已

stitching :图像拼接模块,有了它可以自己生成全景照片

nonfree :受到专利保护的一些算法,其实就是SIFT和SURF

contrib :一些实验性质的算法,考虑在未来版本中加入的

viz :基础的3D渲染模块,其实底层就是著名的3D工具包VTK(Visualization Toolkit)

安装 OpenCV

pip install opencv-python
pip install numpy

Python-OpenCV 基础

图像的表示

在 python 用 numpy 的 array 来表示矩阵

如果是多通道情况,最常见的就是 红绿蓝 三通道

import numpy as np
import cv2
import matplotlib.pyplot as plt

img = np.array([
    [[255, 0, 0], [0, 255, 0], [0, 0, 255]],
    [[255, 255, 0], [255, 0, 255], [0, 255, 255]],
    [[255, 255, 255], [128, 128, 128], [0, 0, 0]],
], dtype=np.uint8)

# 用matplotlib存储
plt.imsave('img_pyplot.jpg', img)

# 用OpenCV存储
cv2.imwrite('img_cv2.jpg', img)

image-20220806203817533

​ img_cv2.jpg

image-20220806203845070

​ img_pyplot.jpg

不管是RGB还是BGR,都是高度×宽度×通道数,H×W×C的表达方式,而在深度学习中,因为要对不同通道应用卷积,所以用的是另一种方式:C×H×W,就是把每个通道都单独表达成一个二维矩阵

基本图像处理

存取图像

读图像用 cv2.imread(),可以按照不同模式读取

一般常用到的是读取单通道灰度图,或者直接默认读取多通道

存图像用 cv2.imwrite(),存的时候没有单通道这一说法,根据保存文件名的后缀和当前的 array 维度,自动判断,压缩格式可以指定存储质量

import cv2

# 读取一张400x600分辨率的图像
color_img = cv2.imread('test.png')
print(color_img.shape)

# 直接读取单通道
gray_img = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE)
print(gray_img.shape)

# 把单通道图片保存后,再读取,仍然是3通道,相当于把单通道值复制到3个通道保存
cv2.imwrite('test_grayscale.jpg', gray_img)
reload_grayscale = cv2.imread('test_grayscale.jpg')
print(reload_grayscale.shape)

# cv2.IMWRITE_JPEG_QUALITY指定jpg质量,范围0到100,默认95,越高画质越好,文件越大
cv2.imwrite('test_imwrite.jpg', color_img, (cv2.IMWRITE_JPEG_QUALITY, 80))

# cv2.IMWRITE_PNG_COMPRESSION指定png质量,范围0到9,默认3,越高文件越小,画质越差
cv2.imwrite('test_imwrite.png', color_img, (cv2.IMWRITE_PNG_COMPRESSION, 5))

缩放,裁剪和补边

缩放通过 cv2.resize 来实现,裁剪则是利用 array 自身的下标截取实现

import cv2

# 读取一个400×600图片
img = cv2.imread('R-C.jpg')

# 缩放成 200 * 200 的方形图片
img_200x200 = cv2.resize(img,(200,200))

# 不直接指定缩放后大小,通过 fx 和 fy 指定缩放比例,0.5 则为长宽的一半
# 等效于img_200x300 = cv2.resize(img,(200,300)),注意指定大小的格式是 宽度,高度
# 插值方法默认是 cv2.INTER_LINEAR,这里指定为最近邻插值
img_200x300 = cv2.resize(img,(0,0),fx=0.5,fy=0.5,interpolation=cv2.INTER_NEAREST)

# 在上张图片的基础上,上下各贴 50 像素的黑边,生成 300x300 的图像
img_300x300 = cv2.copyMakeBorder(img,50,50,0,0,cv2.BORDER_CONSTANT,value=(0,0,0))

patch_tree = img[20:100,-180:-50]
cv2.imwrite('cropped_tree.jpg', patch_tree)
cv2.imwrite('resized_200x200.jpg', img_200x200)
cv2.imwrite('resized_200x300.jpg', img_200x300)
cv2.imwrite('bordered_300x300.jpg', img_300x300)

色调,明暗,直方图和 Gamma曲线

HSV分别是色调(Hue),饱和度(Saturation)和明度(Value)。在HSV空间中进行调节就避免了直接在RGB空间中调节是还需要考虑三个通道的相关性。OpenCV中H的取值是[0, 180),其他两个通道的取值都是[0, 256),

import cv2

img = cv2.imread('fengye.jpg')

# 通过 cv2.cvtColor 把图像从 BGR 转换到 HSV
img_Hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)

# H空间中,绿色的值比黄色的值高一点,所以给每个像素+15,黄色的就会变绿
turn_green_hsv = img_Hsv.copy()
turn_green_hsv[:,:,0] = (turn_green_hsv[:,:,0] + 20) % 180
turn_green_img = cv2.cvtColor(turn_green_hsv,cv2.COLOR_HSV2BGR)
cv2.imwrite("turn_green.jpg",turn_green_img)

# 减小饱和度会让图像损失鲜艳,变得更灰
colorless_hsv = img_Hsv.copy()
colorless_hsv[:,:,1] = 0.5 * colorless_hsv[:,:,1]
colorless_img = cv2.cvtColor(colorless_hsv, cv2.COLOR_HSV2BGR)
cv2.imwrite('colorless.jpg', colorless_img)

# 减小明度为一半
darker_hsv = img_Hsv.copy()
darker_hsv[:, :, 2] = 0.5 * darker_hsv[:, :, 2]
darker_img = cv2.cvtColor(darker_hsv, cv2.COLOR_HSV2BGR)
cv2.imwrite('darker.jpg', darker_img)

HSV 和 RGB,我们难以一眼对像素中值的分布有细致的了解,这里引入 直方图,直方图中的成分过于靠近 0 或 255,可能会出现 暗部细节不足 或者 亮部细节丢失的情况

常用的方法是考虑用 Gamma 变换来提升暗部细节,Gamma 变换是矫正相机直接成像和人眼感受图像差别的一种常用手段

简单来说就是通过非线性变换让图像从对曝光强度的线性响应变得更接近人眼感受的响应

import numpy as np
import cv2
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import *

img = cv2.imread('R-C.jpg')

# 分通道计算每个通道的直方图
# calcHist([img],channels,mask,histSize, ranges)
#mask:掩膜,是一个大小和image一样的np数组,其中把需要处理的部分指定为1,不需要处理的部分指定为0,一般设置为None,表示处理整幅图像
#histSize:使用多少个bin(柱子),一般为256
#ranges:像素值的范围,一般为[0,255]表示0~255
hist_b = cv2.calcHist([img],[0],None,[256],[0,256])
hist_g = cv2.calcHist([img],[1],None,[256],[0,256])
hist_r = cv2.calcHist([img],[2],None,[256],[0,256])

# 定义 Gamma 矫正的函数
def gamma_trans(img,gamma):
    # 具体做法是先归一化到1,然后 gamma 作为指数值求出新的像素值再还原
    gamma_table = [np.power(x/255.0, gamma)*255.0 for x in range(256)]
    gamma_table = np.round(np.array(gamma_table)).astype(np.uint8)
    # np.round 取整函数
    # 实现这个映射用的是 OpenCV 的查表函数
    return cv2.LUT(img,gamma_table)

# 执行 Gamma 矫正,小于 1 的值让暗部细节大量提升,同时亮部细节少量提升 
img_corrected = gamma_trans(img,0.5)
cv2.imwrite('gamma_corrected.jpg',img_corrected)

# 分通道计算矫正后的直方图
hist_b_corrected = cv2.calcHist([img_corrected],[0],None,[256],[0,256])
hist_g_corrected = cv2.calcHist([img_corrected],[1],None,[256],[0,256])
hist_r_corrected = cv2.calcHist([img_corrected],[2],None,[256],[0,256])


# 将直方图可视化
fig = plt.figure()

pix_hists = [
    [hist_b, hist_g, hist_r],
    [hist_b_corrected, hist_g_corrected, hist_r_corrected]
]

pix_vals = range(256)
for sub_plt, pix_hist in zip([121, 122], pix_hists):
    ax = fig.add_subplot(sub_plt, projection='3d')
    for c, z, channel_hist in zip(['b', 'g', 'r'], [20, 10, 0], pix_hist):
        cs = [c] * 256
        ax.bar(pix_vals, channel_hist, zs=z, zdir='y', color=cs, alpha=0.618, edgecolor='none', lw=0)

    ax.set_xlabel('Pixel Values')
    ax.set_xlim([0, 256])
    ax.set_ylabel('Channels')
    ax.set_zlabel('Counts')

plt.show()

图像的仿射变化

仿射变换具体到图像中的应用,主要是对图像的缩放,旋转,剪切,翻转和平移的组合。

仿射变换的矩阵是一个2×3的矩阵,其中左边的2×2子矩阵是线性变换矩阵,右边的2×1的两项是平移项
$$
A=\begin{matrix}
a_{00} & a_{01} \
a_{10} & a_{11} \
\end{matrix},
B=\begin{matrix}
b_{0} \
b_{1} \
\end{matrix},
M=[A,,,,,,B]=\begin{matrix}
a_{00} & a_{01} & b_{0}\
a_{10} & a_{11} & b_{1}\
\end{matrix}
$$
对于图上任一位置,仿射变换执行的是如下操作
$$
T_{affine} = A \begin{matrix}
x \
y \
\end{matrix}+B = M \begin{matrix}
x \
y \
1 \
\end{matrix}
$$
对于图像而言,宽度方向是x,高度方向是y,坐标的顺序和图像像素对应下标一致。所以原点的位置不是左下角而是右上角,y的方向也不是向上,而是向下。在OpenCV中实现仿射变换是通过仿射变换矩阵和 cv2.warpAffine() 这个函数

import cv2
import numpy as np

# 读取一张斯里兰卡拍摄的大象照片
img = cv2.imread('lanka_safari.jpg')

# 沿着横纵轴放大1.6倍,然后平移(-150,-240),最后沿原图大小截取,等效于裁剪并放大
M_crop_elephant = np.array([
    [1.6, 0, -150],
    [0, 1.6, -240]
], dtype=np.float32)

img_elephant = cv2.warpAffine(img, M_crop_elephant, (400, 600))
cv2.imwrite('lanka_elephant.jpg', img_elephant)

# x轴的剪切变换,角度15°
theta = 15 * np.pi / 180
M_shear = np.array([
    [1, np.tan(theta), 0],
    [0, 1, 0]
], dtype=np.float32)

img_sheared = cv2.warpAffine(img, M_shear, (400, 600))
cv2.imwrite('lanka_safari_sheared.jpg', img_sheared)

# 顺时针旋转,角度15°
M_rotate = np.array([
    [np.cos(theta), -np.sin(theta), 0],
    [np.sin(theta), np.cos(theta), 0]
], dtype=np.float32)

img_rotated = cv2.warpAffine(img, M_rotate, (400, 600))
cv2.imwrite('lanka_safari_rotated.jpg', img_rotated)

# 某种变换,具体旋转+缩放+旋转组合可以通过SVD分解理解
M = np.array([
    [1, 1.5, -400],
    [0.5, 2, -100]
], dtype=np.float32)

img_transformed = cv2.warpAffine(img, M, (400, 600))
cv2.imwrite('lanka_safari_transformed.jpg', img_transformed)

基本绘图

from logging.handlers import RotatingFileHandler
from random import triangular
import cv2
import numpy as np

#   定义一块宽600,高400的画布,初始化为白色
canvas = np.zeros((400,600,3),dtype=np.uint8) + 255

#   画一条纵向的正中央的黑色分界线
#   line(画布,标点1,标点2,颜色,宽度)
cv2.line(canvas,(300,0),(300,399),(0,0,0),2)


#   画一条右半部分画面以 150 为界的横向分界线
cv2.line(canvas,(300,150),(600,150),(0,0,0),3)


#   左半部分的右下角画个红色的圈
#   circle(画布,圆心,半径,颜色,线条宽度)
cv2.circle(canvas,(200,300),75,(0,0,255),5)


#   左半部分的左下角画个蓝色的矩形
#   rectangle(画布,左上角端点,右下角端点,颜色,厚度)
cv2.rectangle(canvas,(20,240),(100,360),(255,0,0),thickness=3)


#   定义两个三角形,并执行内部绿色填充
triangles = np.array([
    [(200,240),(145,333),(255,333)],
    [(60,180),(20,237),(100,237)]
])
cv2.fillPoly(canvas,triangles,(0,255,0))
#   空心三角形
#cv2.polylines(canvas, triangles, True, (0, 255, 255), 9)


#   画一个黄色五角星
#   第一步通过旋转角度的办法求出五个顶点
phi = 4 * np.pi / 5
rotations = [[[np.cos(i * phi), -np.sin(i * phi)], [i * np.sin(phi), np.cos(i * phi)]] for i in range(1, 5)]
pentagram = np.array([[[[0, -1]] + [np.dot(m, (0, -1)) for m in rotations]]], dtype=np.float)
#   定义缩放倍数和平移向量把五角星画在左半部分的左上方
pentagram = np.round(pentagram * 80 + np.array([160, 120])).astype(np.int)
#   将5个顶点作为多边形顶点连线
#   polulines(画布,顶点数组,是否闭合,颜色,宽度)
cv2.polylines(canvas, pentagram, True, (0, 255, 255), 9)


#   按像素为间隔从左至右在画面右半部分的上方画出 HSV 空间的色调连续变化
for x in range(302,600):
    color_pixel = np.array([[[round(180*float(x-302)/298), 255, 255]]], dtype=np.uint8)
    line_color = [int(c) for c in cv2.cvtColor(color_pixel, cv2.COLOR_HSV2BGR)[0][0]]
    cv2.line(canvas, (x, 0), (x, 147), line_color)


#   如果定义的圆的线宽大于半径,则等效于画原点,随机在画面右下角的框内生成坐标
np.random.seed(42)
n_pts = 30
pts_x = np.random.randint(310,590,n_pts)
pts_y = np.random.randint(160,390,n_pts)
pts = zip(pts_x,pts_y)
#   画出每个点,颜色随机
for pt in pts:
    pt_color = [int(c) for c in np.random.randint(0,255,3)]
    cv2.circle(canvas,pt,3,pt_color,5)

#   在左半部分最上方打印文字
cv2.putText(canvas,
            'Python-OpenCV Zyh',
            (5,15),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.5,
            (0,0,0),
            1)
cv2.imshow('Example of basic drawing functions', canvas)     
cv2.waitKey()

image-20220819182024948

视频功能

主要两个模块:VideoCapture 用于获取相机设备并捕获图像和视频 和 VideoWriter 用于生成视频

import cv2
import time

interval = 60   #   捕获图像的间隔 ,单位秒
num_frames = 500    #   捕获图像的总帧数
out_fps = 24    #   输出文件的帧数

#   VideoCapture(0) 表示打开默认的相机
cap = cv2.VideoCapture(0)

#   获取捕获的分辨率
size =(int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
       int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))

#   设置要保存视频的编码,分辨率和帧率
video = cv2.VideoWriter(
    "time_lapse.avi",
    cv2.VideoWriter_fourcc('M','P','4','2'),
    out_fps,
    size
)

#   对于一些低画质的摄像头,前面的帧率可能不稳定
for i in range(42):
    cap.read()

#   开始捕获,通过 read 函数获取捕获的帧
try:
    for i in range(num_frames):
        _,frame = cap.read()
        video.write(frame)
    
    #   把每一帧存为文件,比如制作 gif
    #   filename = '(:0>6d).png'.format(i)
    #   cv2.imwrite(filename,frame)

    print('Frame{} is captured.'.format(i))
    time.sleep(interval)
except KeyboardInterrupt:
    #   提前停止捕获
    print('Stopped!{}/{} frames captured!'.format(i,num_frames))

#   释放资源并写入视频文件
video.release()
cap.release()
版权声明:本文为ShibuyaKanon原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ShibuyaKanon/p/16655407.html