【OpenCV2.1】基础知识和绘制图形、OpenCV的色彩空间转换、RGB和BGR、HSV, HSL和YUV、mat(深浅拷贝、访问图像属性、通道的分离与合并、绘制图形)

1 OpenCV的色彩空间
1.1 RGB和BGR
1.2 HSV, HSL和YUV
1.2.1 HSV
1.2.2 HSL
1.2.3 YUV
1.3 色彩空间的转换
2 OpenCV的重要数据结构–Mat
2.1 MAT介绍
2.2 Mat实现深浅拷贝
2.3 访问图像(Mat)的属性
2.4 通道的分离与合并
2.5 绘制图形
写一个程序, 实现按l键之后拖动鼠标绘制直线, 按r键之后拖动鼠标绘制矩形, 按r键拖动鼠标绘制圆形
ndarry的4种常见属性

1 OpenCV的色彩空间

1.1 RGB和BGR

最常见的色彩空间就是RGB, 人眼也是基于RGB的色彩空间去分辨 颜色的.

OpenCV默认使用的是BGR. BGR和RGB色彩空间的区别在于图片在色彩通道上的排列顺序不同.

请添加图片描述
请添加图片描述

显示图片的时候需要注意适配图片的色彩空间和显示环境的色彩空间.比如传入的图片是BGR色彩空间, 显示环境是RBG空间, 就会出现颜色混乱的情况.

1.2 HSV, HSL和YUV

1.2.1 HSV

  • OpenCV用的最多的色彩空间是HSV.
  • Hue: 色相, 即色彩, 如红色, 蓝色. 用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°
  • Saturation: 饱和度, 表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。
  • Value: 明度. 明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。

请添加图片描述

为什么要使用HSV?

方便OpenCV做图像处理.比如根据hue的值就可以判断背景颜色.
请添加图片描述

1.2.2 HSL

HSL和HSV差不多.

  • Hue: 色相

  • Saturation: 饱和度

  • Lightness: 亮度

请添加图片描述

HSL在顶部是纯白的, 不管是什么颜色.
请添加图片描述

HSV和HSL的区别:
请添加图片描述

1.2.3 YUV

YUV,是一种颜色编码方法。常使用在各个视频处理组件中。 YUV在对照片或视频编码时,考虑到人类的感知能力,允许降低色度的带宽。

“Y”表示明亮度(Luminance或Luma),也就是灰阶值,“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

Y’UV的发明是由于彩色电视与黑白电视的过渡时期。

Y’UV最大的优点在于只需占用极少的带宽。

  • 4:4:4表示完全取样。

  • 4:2:2表示2:1的水平取样,垂直完全采样。

  • 4:2:0表示2:1的水平取样,垂直2:1采样。

  • 4:1:1表示4:1的水平取样,垂直完全采样。

请添加图片描述

1.3 色彩空间的转换

  • cvtColor(img, colorspace): 颜色转换的关键API
import cv2

def callback(value):
    # 回调函数:当trackbar的值发生变化时,这个函数会被调用
    pass

# 创建一个名为 'color' 的窗口
cv2.namedWindow('color', cv2.WINDOW_NORMAL)
# 调整窗口的大小为 640x480
cv2.resizeWindow('color', 640, 480)

# 读取本地的图片 'cat.jpeg'
img = cv2.imread('./cat.jpeg')

# 定义一个颜色空间转换列表
# cv2.COLOR_BGR2RGBA 表示将图像从 BGR 转换为 RGBA
# cv2.COLOR_BGR2BGRA 表示将图像从 BGR 转换为 BGRA
# cv2.COLOR_BGR2GRAY 表示将图像从 BGR 转换为灰度
# cv2.COLOR_BGR2HSV 表示将图像从 BGR 转换为 HSV 色彩空间
# cv2.COLOR_BGR2YUV 表示将图像从 BGR 转换为 YUV 色彩空间
color_space = [
    cv2.COLOR_BGR2RGBA, cv2.COLOR_BGR2BGRA, 
    cv2.COLOR_BGR2GRAY, cv2.COLOR_BGR2HSV, 
    cv2.COLOR_BGR2YUV
]

# 创建一个 Trackbar,用于切换颜色空间
# 'trackbar' 是 Trackbar 名称,'color' 是它所属的窗口,范围从 0 到 4
cv2.createTrackbar('trackbar', 'color', 0, 4, callback)

while True:
    # 获取当前 trackbar 的位置,trackbar 的值决定选择哪个颜色空间转换
    index = cv2.getTrackbarPos('trackbar', 'color')
    
    # 使用 cv2.cvtColor 将图像转换为指定颜色空间
    cvt_img = cv2.cvtColor(img, color_space[index])

    # 在窗口中显示转换后的图像
    cv2.imshow('color', cvt_img)
    
    # 等待 10 毫秒,检查是否按下 'q' 键,如果按下则退出循环
    key = cv2.waitKey(10)
    if key == ord('q'):
        break

# 关闭所有窗口并释放资源
cv2.destroyAllWindows()

2 OpenCV的重要数据结构–Mat

2.1 Mat介绍

Mat是OpenCV在C++语言中用来表示图像数据的一种数据结构.在python中转化为numpy的ndarray.

  • Mat由header和data组成, header中记录了图片的维数, 大小, 数据类型等数据.
    请添加图片描述
    请添加图片描述
    请添加图片描述

2.2 Mat实现深浅拷贝

  • Mat共享数据

    请添加图片描述

在python中Mat数据对应numpy的ndarray, 使用numpy提供的深浅拷贝方法即可实现Mat的拷贝.

import cv2
import numpy as np

# 读取图片 'cat.jpeg' 并存储在 img 中,图片数据是一个 NumPy ndarray
img = cv2.imread('./cat.jpeg')

# 浅拷贝:创建 img 的浅拷贝,使用 .view() 方法
# 浅拷贝的特点是两个数组共享相同的内存,修改其中一个,另一个也会发生变化
img2 = img.view()

# 深拷贝:创建 img 的深拷贝,使用 .copy() 方法
# 深拷贝创建了独立的副本,修改原图 img,不会影响 img3
img3 = img.copy()

# 对原始图像 img 的局部区域进行修改,将 (10, 100) 到 (100, 100) 区域的像素值
# 修改为红色(BGR 色彩空间中的 [0, 0, 255] 表示红色)
img[10:100, 10:100] = [0, 0, 255]

# cv2.imshow('img', img)
# cv2.imshow('img2', img2)
# cv2.imshow('img3', img3)

# 此时,img2 和 img 是浅拷贝的关系,因此 img2 也会显示相同的修改
# img3 是深拷贝,因此它不会受到影响,仍然是修改前的图片

# 将 img, img2 和 img3 进行横向堆叠(水平拼接)以便进行对比
# np.hstack() 用于将多个数组水平堆叠
cv2.imshow('img', np.hstack((img, img2, img3)))

# 等待用户按键,按下任意键后关闭所有窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

2.3 访问图像(Mat)的属性

OpenCV中的Mat在python中已经转化为ndarray, 通过ndarray的属性即可访问Mat图像的属性

import cv2
import numpy as np

img = cv2.imread('cat.jpeg')

#shape属性中包括了三个信息
#高度,长度 和 通道数
print(img.shape)

#图像占用多大空间
#高度 * 长度 * 通道数
print(img.size)

#图像中每个元素的位深
print(img.dtype)

2.4 通道的分离与合并

  • split(mat)分割图像的通道
  • merge((ch1,ch2, ch3)) 融合多个通道
import cv2
import numpy as np

# 创建一个大小为 480x640,3 通道的全黑图像
# np.zeros 用来创建一个全 0 的 NumPy 数组,图像的大小是 480 行,640 列,3 个颜色通道 (BGR)
# np.uint8 表示像素值范围为 [0, 255],即 8 位无符号整数
img = np.zeros((480, 640, 3), np.uint8)

# 分割通道:将 BGR 图像的 3 个颜色通道(蓝色,绿色,红色)分割成单独的数组
# b, g, r 分别代表蓝色通道、绿色通道和红色通道
b, g, r = cv2.split(img)

# 修改蓝色和绿色通道中的部分区域
# 将 (10, 100) 到 (100, 100) 这个区域的像素值设置为最大值 255,表示这些区域会显示蓝色和绿色
b[10:100, 10:100] = 255  # 蓝色区域
g[10:100, 10:100] = 255  # 绿色区域

# 合并通道:将分割后的 b、g、r 通道重新合并为一个 3 通道图像
# img2 是一个包含修改后蓝色和绿色区域的完整图像
img2 = cv2.merge((b, g, r))

# 显示分割后的蓝色通道和绿色通道
# 使用 np.hstack() 水平拼接蓝色和绿色通道,方便同时显示和对比
cv2.imshow('img', np.hstack((b, g)))

# 显示原始全黑图像和合并后的图像 img2
# 原始图像 img 是全黑的,img2 包含蓝色和绿色的修改区域
cv2.imshow('img2', np.hstack((img, img2)))

# 等待用户按下任意键后关闭所有窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

2.5 绘制图形

利用OpenCV提供的绘制图形API可以轻松在图像上绘制各种图形, 比如直线, 矩形, 圆, 椭圆等图形.

  • line(img, pt1, pt2, color, thickness, lineType, shift) 画直线

    • img: 在哪个图像上画线
    • pt1, pt2: 开始点, 结束点. 指定线的开始与结束位置
    • color: 颜色
    • thickness: 线宽
    • lineType: 线型.线型为-1, 4, 8, 16, 默认为8
    • shift: 坐标缩放比例.
  • rectangle() 参数同上 画矩形

  • circle(img, center, radius, color[, thickness[, lineType[, shift]]]) 中括号内参数表示可选参数. 画圆

  • ellipse(img, 中心点, 长宽的一半, 角度, 从哪个角度开始, 从哪个角度结束,…)
    请添加图片描述

  • polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]) 画多边形

  • fillPoly 填充多边形

  • putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]]) 绘制文本

    • text 要绘制的文本
    • org 文本在图片中的左下角坐标
    • fontFace 字体类型即字体
    • fontScale 字体大小

画直线

import cv2
import numpy as np

# 创建一个大小为 480x640,3 通道的全黑背景图像
# np.zeros 用于创建一个全 0 的 NumPy 数组,图像大小为 480 行,640 列,3 个颜色通道 (BGR)
img = np.zeros((480, 640, 3), np.uint8)

# 画第一条直线
# cv2.line(img, pt1, pt2, color, thickness, lineType, shift)
# 参数解释:
# img:绘制直线的图像
# pt1:直线的起点 (10, 20)
# pt2:直线的终点 (300, 400)
# color:直线颜色 (0, 0, 255) 表示红色(BGR 色彩空间)
# thickness:直线的粗细,值为 5 像素
# lineType:线型,值为 4,表示抗锯齿的连线
cv2.line(img, (10, 20), (300, 400), (0, 0, 255), 5, 4)

# 画第二条直线
# 起点 (80, 100),终点 (380, 480),颜色也是红色
# lineType 参数为 16,表示抗锯齿
cv2.line(img, (80, 100), (380, 480), (0, 0, 255), 5, 16)

# 显示绘制的图像
cv2.imshow('draw', img)

# 等待用户按下任意键关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

画矩形、画圆、画椭圆、画多边形

import cv2
import numpy as np

# 创建一个大小为 480x640,3 通道的全黑背景图像
# np.zeros 用于创建一个全 0 的 NumPy 数组,图像大小为 480 行,640 列,3 个颜色通道 (BGR)
img = np.zeros((480, 640, 3), np.uint8)

# 画矩形
# cv2.rectangle(img, (80, 100), (180, 180), (0, 255, 0), 5)

# 画圆, 需传入圆心左边,半径
# circle(img, center, radius, color[, thickness[, lineType[, shift]]]) 中括号内参数表示可选参数. 画圆
# cv2.circle(img, (320, 240), 100, (0, 0, 255), 5, 16)

# 绘制椭圆
# cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness, lineType)
# 参数解释:
# img:绘制椭圆的图像
# center:椭圆的中心点坐标,这里是 (320, 240)(图像中心)
# axes:椭圆的长轴和短轴半径,(100, 50) 表示椭圆的长轴半径为 100,短轴半径为 50
# angle:椭圆旋转的角度,这里是 45 度
# startAngle:椭圆的起始角度,这里是 0 度,从水平右侧开始
# endAngle:椭圆的终止角度,这里是 360 度,表示绘制完整的椭圆
# color:椭圆的颜色,(0, 0, 255) 表示红色(BGR 色彩空间)
# thickness:椭圆的边框厚度,这里是 5 像素
# lineType:线型,这里为 16,表示抗锯齿的线条,绘制更加平滑
# cv2.ellipse(img, (320, 240), (100, 50), 45, 0, 360, [0, 0, 255], 5, 16)

# 创建一个包含多边形顶点的数组
# np.array 创建了一个包含 3 个顶点的 NumPy 数组,顶点坐标为 (250, 100), (150, 300), (50, 280)
# np.int32 表示数组的数据类型为 32 位整数,适合用于图像处理中的坐标表示
pts = np.array([(250, 100), (150, 300), (50, 280)], np.int32)
print(pts)

# 绘制多边形
# cv2.polylines(img, pts, isClosed, color, thickness)
# 参数解释:
# img:要绘制多边形的图像
# [pts]:多边形的顶点,注意这里需要把顶点数组包装在一个列表里,以便处理多个多边形
# isClosed:是否闭合多边形,True 表示闭合,即自动连接最后一个点和第一个点
# color:多边形的颜色,这里是 (0, 0, 255),表示红色(BGR 色彩空间)
# thickness:线条的粗细,这里是 5 像素
# cv2.polylines(img, [pts], True, (0, 0, 255), 5)  # pts是3维的

# 使用 cv2.fillPoly 填充多边形
# cv2.fillPoly(img, pts, color)
# 参数解释:
# img:要填充多边形的图像
# [pts]:多边形的顶点数组,注意这里也需要将顶点数组包装在一个列表里,以便函数可以处理多个多边形
# color:填充多边形的颜色,这里是 (0, 0, 255),表示红色(BGR 色彩空间)
# cv2.fillPoly(img, [pts], (0, 0, 255))

# 在图像上绘制文本
# cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType)
# 参数解释:
# img:要绘制文本的图像
# 'Hello OpenCV':要绘制的文本内容
# (10, 100):文本的起始位置坐标,文本左下角的位置在 (10, 100)
# cv2.FONT_HERSHEY_COMPLEX:字体类型,这里使用的是复杂字体
# 2:字体大小缩放因子,值为 2,表示字体相对较大
# [0, 0, 255]:文本颜色,这里是红色(BGR 色彩空间)
# thickness:文本的线条厚度,默认为 1,表示文本边缘的粗细
# lineType:线条类型,默认为 8,表示普通线条
cv2.putText(img, 'Hello OpenCV', (10, 100), cv2.FONT_HERSHEY_COMPLEX, 2, [0, 0, 255])

# 显示绘制的图像
cv2.imshow('draw', img)

# 等待用户按下任意键关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

  • 绘制中文 opencv本身不支持, 因为没有中文字体.我们可以借助pillow来实现绘制中文
import cv2
import numpy as np
from PIL import ImageFont, ImageDraw, Image

# 创建一个大小为 500x500,3 通道的全白背景图像
# img = np.full((500, 500, 3), fill_value=255, dtype=np.uint8)
img = cv2.imread('./cat.jpeg')

# 导入字体文件,指定字体路径和字体大小
# "./msyhbd.ttc" 是本地字体文件的路径,30 是字体大小
# 这里使用的是微软雅黑加粗字体,可以根据系统的字体文件选择不同的字体
font = ImageFont.truetype("./msyhbd.ttc", 30)

# 使用 PIL 的 Image 类将 OpenCV 的图像转换为 PIL 的图像对象
img_pil = Image.fromarray(img)

# 创建一个 ImageDraw 对象,便于在 PIL 图像上绘制
draw = ImageDraw.Draw(img_pil)

# 使用 draw.text 方法在 PIL 图像上绘制中文文本
# 参数解释:
# (100, 150):文本的起始位置坐标
# '你好,我好,大家好':要绘制的中文文本
# font:指定的字体对象
# fill=(0, 255, 0, 0):文本颜色,(0, 255, 0) 表示绿色,第四个 0 表示透明度(忽略)
draw.text((100, 150), '你好,我好,大家好', font=font, fill=(0, 255, 0, 0))

# 将 PIL 图像重新转换为 NumPy 数组,即变回 OpenCV 的格式
img = np.array(img_pil)

# 使用 OpenCV 显示图像
cv2.imshow('img', img)

# 等待用户按下任意键后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

写一个程序, 实现按l键之后拖动鼠标绘制直线, 按r键之后拖动鼠标绘制矩形, 按r键拖动鼠标绘制圆形

import cv2
import numpy as np

# 定义一些全局变量来跟踪鼠标事件和形状
drawing = False  # 是否正在绘制,初始状态为 False 表示未开始绘制
mode = 'line'  # 当前模式:'line' 表示画直线,'rectangle' 表示画矩形,'circle' 表示画圆
start_point = (-1, -1)  # 起点坐标,初始为 (-1, -1) 表示尚未点击鼠标

# 定义鼠标回调函数,用于在窗口中绘制不同的形状
def draw_shape(event: int, x: float, y: float, flags, param):
    """
    鼠标回调函数,用于根据鼠标事件在图像上绘制不同的形状。
    :param event: 表示鼠标事件类型,例如按下、移动和释放。
    :param x: 当前鼠标的 x 坐标。
    :param y: 当前鼠标的 y 坐标。
    :param flags: 特定事件时的标志位(例如是否按下 Ctrl 或 Shift 键)。
    :param param: 传递的额外参数。
    """
    global start_point, drawing, mode, img_copy

    if event == cv2.EVENT_LBUTTONDOWN:
        # 当鼠标左键按下时触发的事件
        # cv2.EVENT_LBUTTONDOWN:左键按下事件,用于开始绘制图形。
        drawing = True  # 标记正在绘制
        start_point = (x, y)  # 记录起点坐标,即鼠标点击的位置
        img_copy = img.copy()  # 复制当前图像,用于后续绘制的临时显示

    elif event == cv2.EVENT_MOUSEMOVE:
        # 当鼠标移动时触发的事件
        # cv2.EVENT_MOUSEMOVE:鼠标移动事件,当绘制状态为 True 时,不断更新图像显示
        if drawing:
            img_copy = img.copy()  # 在每次鼠标移动时,恢复原始图像以便绘制新的形状
            if mode == 'line':
                # 当前模式为 'line',绘制直线
                cv2.line(img_copy, start_point, (x, y), (0, 255, 0), 2)  # 绿色线条,线条宽度为 2 像素
            elif mode == 'rectangle':
                # 当前模式为 'rectangle',绘制矩形
                cv2.rectangle(img_copy, start_point, (x, y), (0, 0, 255), 2)  # 红色矩形,边框宽度为 2 像素
            elif mode == 'circle':
                # 当前模式为 'circle',绘制圆形
                radius = int(np.sqrt((x - start_point[0]) ** 2 + (y - start_point[1]) ** 2))  # 计算圆的半径
                cv2.circle(img_copy, start_point, radius, (255, 0, 0), 2)  # 蓝色圆形,边框宽度为 2 像素

    elif event == cv2.EVENT_LBUTTONUP:
        # 当鼠标左键释放时触发的事件
        # cv2.EVENT_LBUTTONUP:左键松开事件,表示绘制完成,将最终形状固定到图像上
        drawing = False  # 绘制结束
        if mode == 'line':
            # 固定最终绘制的直线
            cv2.line(img, start_point, (x, y), (0, 255, 0), 2)  # 用绿色绘制线条
        elif mode == 'rectangle':
            # 固定最终绘制的矩形
            cv2.rectangle(img, start_point, (x, y), (0, 0, 255), 2)  # 用红色绘制矩形
        elif mode == 'circle':
            # 固定最终绘制的圆形
            radius = int(np.sqrt((x - start_point[0]) ** 2 + (y - start_point[1]) ** 2))  # 计算最终圆的半径
            cv2.circle(img, start_point, radius, (255, 0, 0), 2)  # 用蓝色绘制圆形

# 初始化黑色背景图片
img = np.zeros((600, 800, 3), np.uint8)  # 创建 600x800 像素的黑色背景图像

# 创建一个窗口,显示图像
cv2.namedWindow('draw shape')

# 绑定鼠标回调函数到窗口,使得鼠标事件可以与窗口中的图像互动
cv2.setMouseCallback('draw shape', draw_shape)

# 主循环
while True:
    # 显示当前图像。如果正在绘制过程中,显示临时绘制图像,否则显示固定图像
    cv2.imshow('draw shape', img_copy if drawing else img)
    key = cv2.waitKey(1) & 0xFF  # 监听键盘事件,1 毫秒刷新一次
    if key == ord('q'):  # 按 'q' 键退出程序
        break
    elif key == ord('l'):  # 按 'l' 键切换到绘制直线模式
        mode = 'line'
    elif key == ord('r'):  # 按 'r' 键切换到绘制矩形模式
        mode = 'rectangle'
    elif key == ord('c'):  # 按 'c' 键切换到绘制圆形模式
        mode = 'circle'

# 关闭所有 OpenCV 创建的窗口
cv2.destroyAllWindows()


实现结果如下:
在这里插入图片描述

ndarry的4种常见属性

ndarry的4种常见属性

NumPyndarray 是一个多维数组对象,它有很多属性可以帮助理解和操作数组。以下是 ndarray 对象的 4 种常见属性:

1. shape

  • 定义:表示数组的形状,以一个元组的形式给出每个维度的大小。
  • 用途:用来获取或修改数组的形状。
  • 示例
    import numpy as np
    arr = np.array([[1, 2, 3], [4, 5, 6]])
    print(arr.shape)  # 输出: (2, 3),表示数组有2行3列
    

2. dtype

  • 定义:数组中元素的数据类型,如 int32, float64, bool 等。
  • 用途:查看数组中存储元素的数据类型。
  • 示例
    arr = np.array([1, 2, 3])
    print(arr.dtype)  # 输出: int64
    

3. ndim

  • 定义:表示数组的维数(轴数),即数组是几维的。
  • 用途:查看数组的维度。
  • 示例
    arr = np.array([1, 2, 3])
    print(arr.ndim)  # 输出: 1 表示一维数组
    
    arr_2d = np.array([[1, 2], [3, 4]])
    print(arr_2d.ndim)  # 输出: 2 表示二维数组
    

4. size

  • 定义:数组中元素的总数量,即数组所有维度大小的乘积。
  • 用途:查看数组中包含的元素个数。
  • 示例
    arr = np.array([[1, 2], [3, 4]])
    print(arr.size)  # 输出: 4 表示数组中有4个元素
    

总结

  1. shape:数组的形状(如 (rows, columns))。
  2. dtype:数组元素的数据类型。
  3. ndim:数组的维度(几维数组)。
  4. size:数组中元素的总数量。

这些属性帮助我们快速了解和操作 NumPy 数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值