网格数据
早期计算机处理的数据有2种:文本数据、图像数据。其中文本数据又称为序列数据,图像数据又称为网格数据。
计算机视觉(Computer Vision)是人工智能的一个重要分支,它研究如何让计算机“看”和“理解”图像和视频。也叫机 器视觉(Machine Vision)。
除了纯视觉方向外,还有有很多令人兴奋的混合方向!
混合方向 | 应用场景 | 核心技术 | 典型案例 |
---|---|---|---|
视觉 + 自然语言处理 | 图像描述生成、视觉问答、多模态搜索 | Vision Transformer、CLIP、GPT-4V、LLaVA | ChatGPT视觉功能、Google Lens、图片搜索 |
视觉 + 运动控制 | 自动驾驶、机器人导航、无人机飞行控制 | SLAM、视觉里程计、路径规划 | 特斯拉FSD、波士顿动力机器人、大疆无人机 |
视觉 + 生物医学 | 疾病诊断、手术导航、药物发现 | CT/MRI图像分析、病理切片分析、三维重建 | 肺结节检测、皮肤癌诊断、眼底病变分析 |
视觉 + 三维重建 | 建筑建模、文物保护、虚拟旅游 | 立体视觉、结构光、NeRF、3D Gaussian Splatting | iPhone激光雷达、Matterport 3D扫描 |
早在深度学习之前,人们就开始探索图像的处理了,OpenCV 诞生于深度学习之前,是功能最全面的开源库。它提供了从最基础的图像读写、处理到复杂的特征匹配、目标跟踪、校准等一系列工具。其核心优势在于传统的计算机视觉算法,如图像处理、滤波、形态学操作等。
它向前承接传统以数组为单位的图像处理,向后允许你加载一些现成的算法、模型,是传统图像处理与深度学习之间重要的桥梁。
安装
opencv(opensource computer vision)开源计算机视觉库,包含大量的图像处理函数。
opencv-python需要根据环境选择正确的包
有4个不同的包,你只能选择其中的一个。不要在同一环境中安装多个不同的包。
所有包都使用相同的名称空间(cv2
)。如果你在同一环境中安装了多个不同的包,请使用 pip uninstall
卸载所有包,然后只重新安装一个包。
a. 针对标准桌面环境的包(适用于 Windows、macOS、几乎所有 GNU/Linux 发行版)
- 选项 1 - 主模块包:
pip install opencv-python
- 选项 2 - 完整包(包含主模块和 contrib/extra 模块):
pip install opencv-contrib-python
(请参考 OpenCV 文档 中的 contrib/extra 模块列表)
b. 针对服务器(无窗口)环境的包(例如 Docker、云环境等),无 GUI 库依赖
这些包比前面两种包更小,因为它们不包含任何图形用户界面功能(未编译 Qt 或其他 GUI 组件)。这意味着这些包避免了对 X11 库的庞大依赖,从而例如可以生成更小的 Docker 镜像。如果你不使用 cv2.imshow
等函数,或者你使用其他包(例如 PyQt)来构建界面而非 OpenCV,则应始终使用这些包。
- 选项 3 - 无窗口主模块包:
pip install opencv-python-headless
- 选项 4 - 无窗口完整包(包含主模块和 contrib/extra 模块):
pip install opencv-contrib-python-headless
(请参考 OpenCV 文档 中的 contrib/extra 模块列表)
OpenCv作为开源软件,自然有大量的教程,我读过一些纸质书籍,也看过 一些开源教程,总体来说,对于入门与进阶来说,需要的是细致的基础讲解、完整的处理流程,非常推荐官方的OpenCV-Python教程。
图像读取与显示
图像读取
函数签名:cv2.imread(filename, flags=cv2.IMREAD_COLOR) -> image
参数说明:
filename
:要读取的文件名flags
:读取标志,默认值为cv2.IMREAD_COLOR,表示读取为BGR格式。可选参数:- cv2.IMREAD_COLOR:读取为BGR格式,默认值。对应数值为1。
- cv2.IMREAD_GRAYSCALE:读取为灰度图,对应数值为0。
- cv2.IMREAD_UNCHANGED:读取为原图,包含alpha通道,对应数值为-1。
返回值:
- 返回读取的图像对象, 如果读取失败, 不报错,返回None
-
CV2默认使用系统编码,如果文件路径含中文无法正常读取,需要把系统编码改为
utf-8
。
图像对象的常用属性:
shape
:图像的形状,(height, width, channels) 其中channels为3、4,表示通道数为RGB、RGBA(A是透明度),如果是灰度、二值图,通道数为1的情况会只返回(height, width)。size
:图像的像素数,height * width * channelsdtype
:图像的数据类型ndim
:图像的维度itemsize
:图像的元素大小nbytes
:图像的内存大小
import cv2
path= "10.jpg"
img = cv2.imread(path)
print(img.shape)
图像保存
函数签名:cv2.imwrite(filename, img, [params])
参数说明:
filename
:要保存的文件名img
:要保存的图像对象(数据)params
:可选参数,用于指定保存的格式和质量,可选参数:- cv2.IMWRITE_JPEG_QUALITY:JPEG图像质量,范围为0-100,默认值为95
- cv2.IMWRITE_PNG_COMPRESSION:PNG图像压缩级别,范围为0-9,默认值为3
- cv2.IMWRITE_PXM_BINARY:PPM/PGM/PBM图像格式,默认值为False
import cv2
path= "10.jpg"
img = cv2.imread(path)
cv2.imwrite('new_img.jpg',img, [cv2.IMWRITE_JPEG_QUALITY, 100])
图像显示、等待按键、关闭窗口
cv2.imshow函数签名:cv2.imshow(winname, img)
参数说明:
winname
:窗口名称img
:要显示的图像对象
cv2.waitKey函数签名:cv2.waitKey([delay]) -> retval
参数说明:
delay
:等待时间,单位为毫秒。当值为0时,表示无限等待。
返回值:
- 返回按键的ASCII码(大小写敏感),如果到时间没有按键按下,则返回-1。
注意:
- 如果设置了中文输入法,键盘的捕获会被输入法占用,导致无法正常等待按键按下。
- 如果弹出多个相同名称的窗口,只会显示一个窗口,窗口内的图像内容会被覆盖。
上面两个函数是成对出现的,cv2.imshow
用于显示图像,cv2.waitKey
用于等待按键按下或者到时间自动关闭窗口。
下面两个函数都表示关闭窗口,区别在于destroyWindow
是关闭指定窗口,而destroyAllWindows
是关闭所有窗口。
destroyWindow函数签名:cv2.destroyWindow(winname)
参数说明:
winname
:要关闭的窗口名称
destroyAllWindows函数签名:cv2.destroyAllWindows()
import cv2
path= "10.jpg"
img = cv2.imread(path)
# 显示图片, 参数:img: 图像对象, 'Image': 窗口名称
# 如果窗口名称重复,则不会创建新的窗口,而是将图像显示在已有的窗口中
cv2.imshow('Image', img)
# 等待按键按下,0表示无限等待,其他数字表示等待时间(毫秒)
# 这个方法会阻塞程序,直到按键按下,返回值为按键的ASCII码。如果设置了中文输入法,键盘的捕获会被输入法占用,导致无法正常等待按键按下。
key = cv2.waitKey(0)
# 如果按键为q,则退出
if key == ord('q'):
# 关闭指定窗口
cv2.destroyWindow('Image')
# 关闭所有窗口
cv2.destroyAllWindows()
动态图像
视频读取与显示
# cv2.VideoCapture(参数):参数可以是整数、字符串、流媒体
# 整数:表示摄像头的编号
# 0:表示默认摄像头
# 1:表示第二个摄像头
# 字符串:表示视频文件的路径(绝对相对都可以):1.mp4
# 流媒体:表示视频流: http://192.168.1.100:8080
cap = cv2.VideoCapture(0) # 摄像头的语柄
if not cap.isOpened():
print("Error: Could not open video.")
cap.release() # 释放摄像头
while True:
ret,frame = cap.read() # 读取视频帧
# ret:表示是否读取成功(True/False)
# frame:表示读取到的视频帧数据(numpy数组)
if not ret:
print("Error: Could not read frame.")
break
cv2.imshow("frame",frame) # 显示视频帧。同个窗口名称,会覆盖窗口内容,而不是新开窗口。
if cv2.waitKey(1) == ord('q'): # 按q键退出循环
break
cap.release() # 释放摄像头
cv2.destroyAllWindows() # 关闭所有窗口
视频保存
函数签名:cv2.VideoWriter(filename, fourcc, fps, frameSize)
参数说明:
- filename:视频文件名
- fourcc:视频编码格式
- fps:帧率
- frameSize:帧大小
import cv2
import numpy as np
# cv2.VideoCapture(参数):参数可以是整数、字符串、流媒体
# 整数:表示摄像头的编号
# 0:表示默认摄像头
# 1:表示第二个摄像头
# 字符串:表示视频文件的路径(绝对相对都可以):1.mp4
# 流媒体:表示视频流:rtsp://192.168.1.100:8554/test 或者 http://192.168.1.100:8080
cap = cv2.VideoCapture(0) # 摄像头的语柄
# 视频的编码格式
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
# 视频的输出路径
out = cv2.VideoWriter('output.mp4', fourcc, 20.0, (640,480))
if not cap.isOpened():
print("Error: Could not open video.")
cap.release() # 释放摄像头
while True:
ret,frame = cap.read() # 读取视频帧
# ret:表示是否读取成功(True/False)
# frame:表示读取到的视频帧数据(numpy数组)
if not ret:
print("Error: Could not read frame.")
break
out.write(frame) # 写入视频帧
cv2.imshow("frame",frame) # 显示视频帧。同个窗口名称,会覆盖窗口内容,而不是新开窗口。
if cv2.waitKey(1) == ord('q'): # 按q键退出循环
break
out.release() # 释放视频
cap.release() # 释放摄像头
cv2.destroyAllWindows() # 关闭所有窗口
图像绘制
绘制直线、矩形
line
函数签名:cv2.line(img, pt1, pt2, color, thickness, lineType, shift)
rectangle
函数签名:cv2.rectangle(img, pt1, pt2, color, thickness, lineType, shift)
参数说明:
img
:图像对象pt1
:起点坐标pt2
:终点坐标color
:颜色thickness
:线宽lineType
:线型shift
:位移,默认为0,表示坐标是整数,如果大于1,则表示坐标是浮点数,例如shift=2,则表示坐标是浮点数。可用于实现亚像素精度绘图。-
对于矩形来说,
pt1
是左上角,pt2
是右下角。
圆形、椭圆
circle
函数签名:cv2.circle(img, center, radius, color, thickness, lineType, shift)
ellipse
函数签名:cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness, lineType, shift)
参数说明:
img
:图像对象center
:圆心坐标radius
:半径axes
:椭圆长轴和短轴长度angle
:椭圆旋转角度startAngle
:椭圆起始角度endAngle
:椭圆结束角度color
:颜色thickness
:线宽lineType
:线型shift
:位移,默认为0,表示坐标是整数,如果大于1,则表示坐标是浮点数,例如shift=2,则表示坐标是浮点数。可用于实现亚像素精度绘图。
多边形
polylines函数签名:cv2.polylines(img, pts, isClosed, color, thickness, lineType, shift)
参数说明:
img
:图像对象pts
:顶点列表isClosed
:是否闭合color
:颜色thickness
:线宽lineType
:线型shift
:位移,默认为0,表示坐标是整数,如果大于1,则表示坐标是浮点数,例如shift=2,则表示坐标是浮点数。可用于实现亚像素精度绘图。
# 导入库
import cv2
# 读取图片
img = cv2.imread('10.jpg')
# 绘制直线
# 参数:img: 图像对象, (100, 100): 起点坐标, (200, 200): 终点坐标, (0, 0, 255): 颜色, 2: 线宽
cv2.line(img, (100, 100), (200, 200), (0, 0, 255), 2)
# 绘制矩形
# 参数:img: 图像对象, (100, 100): 左上角坐标, (200, 200): 右下角坐标, (0, 0, 255): 颜色, 2: 线宽
cv2.rectangle(img, (100, 100), (200, 200), (0, 0, 255), 2)
# 绘制圆形
# 参数:img: 图像对象, (150, 150): 圆心坐标, 50: 半径, (0, 0, 255): 颜色, 2: 线宽
cv2.circle(img, (150, 150), 50, (0, 0, 255), 2)
# 绘制多边形
# 参数:img: 图像对象, [pts]: 顶点列表, True: 是否闭合, (0, 0, 255): 颜色, 2: 线宽
pts = np.array([[100, 100], [200, 100], [200, 200], [100, 200]])
cv2.polylines(img, [pts], True, (0, 0, 255), 2)
# 绘制椭圆
# 参数:img: 图像对象, (150, 150): 椭圆中心坐标, (100, 50): 椭圆长轴和短轴长度, 0: 椭圆旋转角度, 0: 椭圆起始角度, 360: 椭圆结束角度, (0, 0, 255): 颜色, 2: 线宽
cv2.ellipse(img, (150, 150), (100, 50), 0, 0, 360, (0, 0, 255), 2)
# 显示图片
cv2.imshow('Image', img)
# 等待按键按下
cv2.waitKey(0)
# 关闭所有窗口
cv2.destroyAllWindows()
绘制文字
函数签名:cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin)
参数说明:
img
:图像对象text
:要绘制的文字org
:文字的左下角坐标fontFace
:字体类型fontScale
:字体缩放比例(大于1时,文字会变大、0~1时,文字会变小,小于0时,文本将倒置)color
:文字颜色thickness
:文字粗细lineType
:线型bottomLeftOrigin
:是否从左下角开始绘制
函数直接作用于图像对象,不需要返回值。
import cv2
# 读取图片
img = cv2.imread('10.jpg')
# 绘制文字:Hello, World! 位置为(100, 100),字体为cv2.FONT_HERSHEY_SIMPLEX,字体缩放比例为1,颜色为红色,粗细为2
cv2.putText(img, 'Hello, World!', (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
# 显示图片
cv2.imshow('Image', img)
# 展示1秒
cv2.waitKey(1000)
# 关闭所有窗口
cv2.destroyAllWindows()
PIL库绘制中文
cv2的在图片上的绘制语法主要缺陷为无法绘制中文,需要使用PIL库来绘制中文。
字体文件:一种可以把码点转换为像素显示的超大型字典。
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
# 打开图片
img = Image.open('imgs/img/10.jpg') # 替换为你的图片路径
# 设置字体(确保路径正确,Windows下常见字体路径如下)
font_path = "simhei.ttf" # 黑体
font_size = 60
font = ImageFont.truetype(font_path, font_size)
# 要绘制的文字
text = "机器视觉"
# 创建绘图对象
draw = ImageDraw.Draw(img)
# 计算文字尺寸
text_width, text_height = draw.textsize(text, font=font)
# 计算图片中心
img_width, img_height = img.size
x = (img_width - text_width) // 2
y = (img_height - text_height) // 2
# 绘制文字(可设置颜色和描边等)
draw.text((x, y), text, font=font, fill=(255, 0, 0)) # 红色
# 保存或显示图片
img.show()
# img.save('output.jpg')
图像运算
加减法的前提是两张图片的尺寸相同。
图像加法、图像减法
import cv2
import numpy as np
path = "xxx.bmp"
def draw_add(path):
img = cv2.imread(path)
img_height, img_width = img.shape[:2]
# 创建一个全白的mask_img
mask_img = np.zeros((img_height, img_width, 3), dtype=np.uint8) + 255
# 在mask_img上写上Showcase
cv2.putText(mask_img, "Showcase", (img_width//4, img_height//2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 5)
# 将mask_img和img相加
img3 = cv2.add(img, mask_img)
cv2.imwrite(f"add_{path}", img3)
def draw_subtract(path):
img = cv2.imread(path)
img_height, img_width = img.shape[:2]
# 创建一个全白的mask_img
mask_img = np.zeros((img_height, img_width, 3), dtype=np.uint8) + 255
# 在mask_img上画一个圆
cv2.circle(mask_img, (img_width//2, img_height//2), int(min(img_width, img_height)*0.45), (0, 0, 0), -1)
# 将mask_img和img相减
img3 = cv2.subtract(img, mask_img)
cv2.imwrite(f"subtract_{path}", img3)
draw_add(path)
draw_subtract(path)
图像加权cv2.addWeighted
函数签名:cv2.addWeighted(src1, alpha, src2, beta, gamma) -> dst
参数说明:
src1
:图像1alpha
:图像1的强度,范围为0-1src2
:图像2beta
:图像2的强度,范围为0-1gamma
:偏移量(即图像在加权后,整体增加的值,一般为0)
图像1和图像2的强度不需要合起来等于1,可以大于1,也可以小于1。
import cv2
import numpy as np
img1 = cv2.imread("green.png")
img2 = cv2.imread("red.png")
img3 = cv2.addWeighted(img1, 1, img2, 1,0)
cv2.imshow("img4", img3)
cv2.waitKey(0)
基础图像操作
图像缩放
函数签名:cv2.resize(src, dsize, fx, fy, interpolation) -> dst
参数说明:
src
:源图像dsize
:目标图像大小,如果为None,则根据fx和fy计算,传入的是(width, height)fx
:水平缩放比例fy
:垂直缩放比例interpolation
:插值方式,默认值为cv2.INTER_LINEAR:双线性插值
当resize指定的新尺寸大于原尺寸时,会使用插值方式来填充新产生的像素。
import cv2
import numpy as np
img1 = cv2.imread("xxx.png")
# 固定值缩放
img2 = cv2.resize(img1, (100, 100))
# 按比例缩放
img2 = cv2.resize(img1, dsize = None, fx = 0.5, fy = 0.5)