OpenCV学习笔记

官方文档:https://docs.opencv.org/4.9.0/

Posted by 呆贝斯 on December 12, 2021

简介

OpenCV(开源计算机视觉库)是一个开源库,包含数百种计算机视觉算法。它本质上是一个 C++ API,而不是基于 C 的 OpenCV 1.x API(自 OpenCV 2.4 发布以来,C API 已弃用,并且未使用“C”编译器进行测试)。 OpenCV 具有模块化结构,这意味着该软件包包含多个共享或静态库。

自动内存管理

OpenCV 自动处理所有内存。首先, std::vector 、cv::Mat以及函数和方法使用的其他数据结构具有析构函数,可以在需要时释放底层内存缓冲区。 这意味着析构函数并不总是像 Mat 那样释放缓冲区。他们考虑了可能的数据共享。析构函数递减与矩阵数据缓冲器关联的参考计数器。 当且仅当引用计数器达到零时,即没有其他结构引用同一缓冲区时,缓冲区才会被释放。类似地,当复制 Mat 实例时,并没有真正复制任何实际数据。 相反,引用计数器会增加以记住相同数据的另一个所有者。还有cv::Mat::clone方法可创建矩阵数据的完整副本。

输出数据的自动分配

大多数时候,OpenCV 会自动释放内存,并自动为输出函数参数分配内存。因此,如果函数具有一个或多个输入数组(cv::Mat实例)和一些输出数组, 则输出数组会自动分配或重新分配。输出数组的大小和类型由输入数组的大小和类型确定。如果需要,这些函数会采用额外的参数来帮助确定输出数组的属性。

多线程和可重输入性

当前的 OpenCV 实现是完全可重新输入的。也就是说,不同类实例的相同函数或相同方法可以从不同线程调用。此外,相同的 Mat 可以在不同的线程中使用, 因为引用计数操作使用特定于体系结构的原子指令。

安装

pip安装

pip install opencv-python==3.4.1.15 pip install opencv-contrib-python==3.4.1.15

注意:3.4.2后版本部分算法被申请专利,建议安装3.4.1.15

主要模块

额外模块

core. 核心功能

定义基本数据结构的紧凑模块,包括密集多维数组 Mat 和所有其他模块使用的基本函数。

imgproc. 图像处理

图像处理模块,包括线性和非线性图像过滤、几何图像变换(调整大小、仿射和透视变形、基于通用表的重新映射)、颜色空间转换、直方图等。

imgcodecs. 图像文件的读写

videoio. 视频输入/输出

highgui. 高级图形用户界面

video. 视频分析

视频分析模块,包括运动估计、背景扣除和对象跟踪算法。

calib3d. 相机标定和3D重建

features2d. 2D特征框架

objdetect. 物体识别

dnn. 深度神经网络模块

ml. 机器学习

flann. 多维空间中的聚类和搜索

photo. 计算摄影

stitching. 图像拼接

gapi. 图形API

模块教程

alphamat. 阿尔法抠图

aruco. Aruco标记, 模块功能已迁移至objdetect模块

bgsegm. 改进的背景-前景分割方法

bioinspired. 受生物学启发的视觉模型和衍生工具

cannops. 上升后端的操作

ccalib. 用于3D重建的自定义校准模式

cudaarithm. 矩阵运算

cudabgsegm. 背景分割

cudacodec. 视频编码/解码

cudafeatures2d. 特征检测和描述

cudafilters. 图像过滤

cudaimgproc. 图像处理

cudalegacy. 旧版支持

cudaobjdetect. 物体识别

cudaoptflow. 光流

cudastereo. 立体对应

cudawarping. 图像变形

cudev. 设备层

cvv. 用于计算机视觉程序交互式可视化调试的GUI

datasets. 处理不同数据集的框架

dnn_objdetect. DNN用于物体识别

dnn_superres. DNN用于超分辨率

dpm. 基于可变形零件的模型

face. 人脸分析

freetype. 使用freetype/harfbuzz绘制UTF-8字符串

fuzzy. 基于模糊数学的图像处理

hdf. 分层数据格式输入/输出例程

hfs. 用于高效图像分割的分形特征选择

img_hash. 该模块带来了不同图像哈希算法的实现

intensity_transform. 该模块带来了强度变换算法的实现来调整图像对比度

julia. OpenCV的Julia绑定

line_descriptor. 从图像中提取线条的二进制描述符

mcc. Macbeth图标模块

optflow. 光流算法

ovis. OGRE 3D 可视化工具

phase_unwrapping. 相位展开API

plot. Mat数据的绘图函数

quality. 图像质量分析(IQA)API

rapid. 基于轮廓的3D对象跟踪

reg. 图像配准

rgbd. RGB深度处理

saliency. 显着性API

sfm. 运动结构

shape. 形状距离和匹配

stereo. 立体对应算法

structured_light. 结构光API

superres. 超分辨率

surface_matching. 表面匹配

text. 场景文本检测和识别

tracking. 追踪API

videostab. 视频稳定

viz. 3D展示台

wechat_qrcode. 微信二维码检测器,用于检测和解析二维码

xfeatures2d. 额外的2D特征框架

ximgproc. 扩展图像处理

xobjdetect. 扩展物体识别

xphoto. 附加照片处理算法

基本操作

图像数据读取

import cv2 # opencv读取的格式是BGR
img = cv2.imread("test.jpg"cv.IMREAD_COLOR)
  • cv2.IMREAD_COLOR:彩色图像
  • cv2.IMREAD_GRAYSCALE:灰度图像

图像显示

def cv_show(name, img):
    """图像显示"""
    cv2.imshow(name,img)
    cv2.waitKey(0) # 按任意键退出
    cv2.destroyAllWindows()

图像保存

cv2.imwrite("test.png", img)

视频读取展示

  • cv2.VideoCapture可以捕获摄像头,用数字来控制不同的设备,例如0,1。
  • 如果是视频文件,直接指定路径即可。
vc = cv2.VideoCapture(0)
while 1:
    ret, frame = vc.read()
    cv2.imshow("frame", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
vc.release()
cv2.destroyAllWindows()

截取部分图像数据

cat = img[0:50,0:200]
cv_show("cat",cat)

颜色通道提取

b, g, r = cv2.split(img)
hmerge = np.hstack((b, g, r))
cv_show("hmerge", hmerge)
img_new = cv2.merge((b, g, r))
cv_show("img_new", img_new)

边界填充

top_size, bottom_size, left_size, right_size = (50, 50, 50, 50)
replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
cv_show("img_new", replicate)
  • BORDER_REPLICATE:复制法,也就是复制最边缘像素。
  • BORDER_REFLECT:反射法,对感兴趣的图像中的像素在两边进行复制。例如:fedcba abcdefgh hgfedcb
  • BORDER_REFLECT_101:反射法,也就是以最边缘像素为轴,对称。例如:gfedcb abcdefgh gfedcba
  • BORDER_WRAP:外包装法。例如:cdefgh abcdefgh abcdefg
  • BORDER_CONSTANT:常量法,常数值填充。

数值计算

img_1 = img + 10  # 各像素点数值加1
print(img[:5, :, 0])
print(img_1[:5, :, 0])

print((img + img_1)[:5, :, 0])  # 各像素点数值相加,除256取余
print(cv2.add(img, img_1))  # 各像素点数值相加,超过255取255

修改图片尺寸

img_2 = cv2.resize(img, img.shape[:2])  # 指定尺寸修改
img_3 = cv2.resize(img, (0, 0), fx=3, fy=3)  # 同比例放大
cv_show("img_2", img_2)
cv_show("img_3", img_3)

图像融合

先将图片shape值变为一致,再融合

img = cv2.imread("test.png")
img1 = cv2.imread("test1.png")
img_2 = cv2.resize(img1, img.shape[:2])  # 指定尺寸修改
res = cv2.addWeighted(img, 0.4, img_2, 0.6, 0)
cv_show("res", res)

图像阈值

ret, dst = cv2.threshold(src, thresh, maxval, type)

  • src:输入图,只能输入单通道图像,通常来说为灰度图。
  • dst:输出图。
  • thresh:阈值。
  • maxval:当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
  • type:二值化操作的类型,包含以下五种类型:
    1. cv2.THRESH_BINARY:超过阈值部分取maxval(最大值),否则取0。
    2. cv2.THRESH_BINARY_INV:THRESH_BINARY的反转。
    3. cv2.THRESH_TRUNC:大于阈值部分设为阈值,否则不变。
    4. cv2.THRESH_TOZERO:大于阈值部分不改变,否则设为0。
    5. cv2.THRESH_TOZERO_INV:THRESH_TOZERO的反转。
img_gray = cv2.imread("test.png", cv2.IMREAD_GRAYSCALE)
ret, dst = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
cv_show("dst", dst)

图像平滑

blur = cv2.blur(img, (3,3))  # 均值滤波,简单的平均卷积操作,需要设定核的大小
box = cv2.boxFilter(img,-1,(3,3), normalize=True)  # 方框滤波,基本和均值一样,可以选择归一化
gaussian = cv2.GaussianBlur(img, (5,5), 1)  # 高斯滤波,高斯模糊的卷积核里的数值是满足高斯分布的,相当于更重视中间的
median = cv2.medianBlur(img, 5)  # 中值滤波,相当于用中值代替

形态学-腐蚀操作

kernel = np.ones((5,5), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)
cv_show("erosion", erosion)

形态学-膨胀操作

可以对腐蚀操作进行弥补

kernel = np.ones((5,5), np.uint8)
img_dilate = cv2.dilate(erosion, kernel, iterations=1)
cv_show("img_dilate", img_dilate)

开运算与闭运算

kernel = np.ones((5,5), np.uint8)
# 开运算:先腐蚀,再膨胀
img_open = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv_show("open", img_open)
# 闭运算:先膨胀,在腐蚀
img_close = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv_show("close", img_close)

梯度运算

可以帮助寻找边界

# 梯度运算 = 膨胀 - 腐蚀
img = cv2.imread("test.png")
kernel = np.ones((3, 3), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)
img_dilate = cv2.dilate(erosion, kernel, iterations=1)
gradient = cv2.morphologyEx(img_dilate, cv2.MORPH_GRADIENT, kernel)
cv_show("gradient", gradient)

礼帽与黑帽

kernel = np.ones((3, 3), np.uint8)
# 礼貌 = 原始输入 - 开运算结果
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
# 黑帽 = 闭运算 - 原始输入
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv_show("tophat", tophat)
cv_show("blackhat", blackhat)

图像梯度-Sobel算子*

dst = cv2.Sobel(src, ddepth, dx, dy, ksize)

  • ddepth:图像深度
  • dx和dy:分别表示水平和竖直方向
  • ksize:Sobel算子的大小
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
# 白到黑是正数,黑到白就是负数了,所有的负数会被截断成0,所以要绝对值。
sobelx = cv2.convertScaleAbs(sobelx)
cv_show(sobelx, "sobelx")
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)
cv_show(sobely, "sobely")
# 分别计算x和y,再求和,不建议直接计算。
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_show(sobelxy, "sobelxy")

scharr算子

差异变得更大,变得更敏感

lapkacian算子

对一些变化更敏感,对一些噪音点敏感,需要融合其他方法一块使用,一般不单独使用

Canny边缘检测

  1. 使用高斯滤波器,以平滑图像,滤除噪声。
  2. 计算图像中每个像素点的梯度强度和方向。
  3. 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散影响。
  4. 应用双阈值(Double-Threshold)检测来确定真实和潜在的边缘。
  5. 通过抑制孤立的弱边缘最终完成边缘检测。
img_1 = cv2.Canny(img, 80, 150)
img_2 = cv2.Canny(img, 80, 100)
res = np.hstack((img_1, img_2))
cv_show("res", res)

图像金字塔

  • 高斯金字塔
    1. 向下采样法(缩小)
      • 将Gi与高斯内核卷积
      • 将所有偶数行和列去除
    2. 向上采样法(放大)
      • 将图像在每个方向扩大为原来的两倍,新增的行和列以0填充。
      • 使用先前同样的内核(乘以4)与放大后的图像卷积,获得近似值。
  • 拉普拉斯金字塔
    1. 低通滤波
    2. 缩小尺寸
    3. 放大尺寸
    4. 图像相减

图像轮廓

cv2.findContours(img, mode, method)

mode: 轮廓检索模式

  • RETR_EXTERNAL:只检索最外边轮廓;
  • RETR_LIST:检索所有轮廓,并将其保存到一条链表中;
  • RETR_CCOMP:检索所有轮廓,并将他们组织为两层;顶层是各部分的外部边界,第二层是空洞的边界;
  • RETR_TREE:检索所有轮廓,并重构嵌套轮廓的整个层次;

method:

  • CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有的其他方法输出多边形(顶点的序列)。
  • CHAIN_APPROX_SIMPLE:压缩水平的,垂直的和斜的部分,也就是函数只保留他们的终点部分。

为了更高的准确率,使用二值图像。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度
# 注意需要copy,要不原图会变
draw_img = img.copy()
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)

轮廓特征

cnt = contours[0]
cv2.contours(cnt)  # 面积
cv2.arcLength(cnt, True)  # 周长,True表示闭合的

轮廓近似

epsilon = 0.1*cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)

边界矩形

x, y, w, h = cv2.boundingRect(cnt)
img = cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)

外接圆

(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
img = cv2.circle(img, center, radius, (0,255,0),2)

模板匹配

模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图象被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AB大小,而模板是ab大小,则输出结果的矩阵是(A-a+1)*(B-b+1)。

  • TM_SQDIFF:计算平方不同,计算出来的值越小,越相关。
  • TM_CCORR:计算相关性,计算出来的值越大,越相关。
  • TM_CCOEFF:计算相关系数,计算出来的值越大,越相关。
  • TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关。
  • TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关。
  • TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关。
img = cv2.imread("img.jpg",0)  # 原始图像
template = cv2.imread("template.jpg",0)  # 模板
h, w = template.shape[:2]
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

直方图

cv2.calcHist(images, channels, mask, histSize, ranges)

  • images:原图像的图像格式为uint8或float32.当传入函数时应用中括号“[]”括起来,例如“[ img ]”。
  • channels:如果传入图像的灰度图,它的值就是“[ 0 ]”,如果是彩色图像,传入的参数可以是“[ 0 ]”、“[ 1 ]”、“[ 2 ]”,分别对应着 BGR。
  • mask:掩模图像。统计整幅图像的直方图就把它设为None,但是如果你想统计图像的某一部分的直方图,你就只做一个掩膜图像并使用它。
  • histSize:BIN 的数目。也应用中括号括起来。
  • ranges:像素值范围常为 0 ~ 255。
img = cv2.imread("img.jpg",0)
hist = cv2.calcHist([img], [0], None, [256], [0,256])

直方图均衡化

equ = cv2.equalizeHist(img)
plt.hist(equ.ravel(),256)

自适应直方图均衡化

clach = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
res_clach = clach.apply(img)
res = np.hstack((img, res_clach))

傅里叶变换

我们生活在时间的世界中,早上7:00起来吃早饭,8:00去挤地铁,9:00开始上班。以时间为参照就是时域分析,但是在频域中一切都是静止的。

傅里叶变换作用

  • 高频:变化剧烈的灰度变量,例如边界
  • 低频:变化缓慢的灰度分量,例如一片大海

滤波

  • 低通滤波器:只保留低频,会使得图像模糊
  • 高通滤波器:只保留高频,会使得图像细节增强

opencv中主要就是cv.dft()和cv.idft()函数,输入图像需要先转换成np.float32格式。得到的结果中频率为0的部分会在左上角,通常要转换到中心位置,可以通过shift变换来实现。cv2.dft()返回的结果是双通道的(实部,虚部),通常还需要转换成图像格式才能显示(0,255)。

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

img = cv2.imread("img.jpg",0)

img_float = np.float32(img)

dft = cv2.dft(img_float, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

# 得到灰度图能表示的形式

magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))

plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

信用卡识别卡号

from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
import myutils

# 设置参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="Path to input image")
ap.add_argument("-t", "--template", required=True, help="Path to template OCR_A image")
args = vars(ap.parse_args())

# 指定信用卡类型
FIRST_NUMBER = {
    "3": "American Express",
    "4": "Visa",
    "5": "MasterCard",
    "6": "Discover Card"
}

# 绘图展示
def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
# 读取一个模板图像
img = cv2.imread(args["template"])
cv_show("img", img)
# 灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show("ref", ref)
# 二值图像
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show("ref",ref)
# 计算轮廓
# cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
# 返回的list中每个元素都是图像中的一个轮廓。
ref_,refCnts,hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(ref, refCnts, -1, (0,255,0), 3)
cv_show("img", img)
print(np.array(refCnts).shape)
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0]
digits = {}

###