计算机视觉:2.6~4.5 颜色空间、数据结构与绘图

颜色空间、数据结构与绘图

2.6 RGB和BGR颜色空间

OpenCV的色彩空间

RGB和BGR

最常见的色彩空间就是RGB,人眼也是基于RGB的色彩空间去分辨颜色的。 OpenCV默认使用的是BGR。BGR和RGB色彩空间的区别在于图片在色彩通道上的排列顺序不同。

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

3.1 HSV、HSL、YUV

HSV(HSB)

OpenCV最常用的色彩空间就是HSV

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

为什么使用HSV? 方便OpenCV做图像处理,比如根据HUE的值就可以判断背景颜色。

HSL

  • Hue:色相
  • Saturation:饱和度
  • Lightness:亮度
Pasted image 20220708194058

HSL顶部是纯白色的,不管是什么色相。HSB 与 HSL 在字面意思上是一样的:

  • H:指的是色相(Hue)例如:红色、蓝色;
  • S:指的是饱和度(Saturation),即颜色的纯度;
  • L 和 B:指的是明度(Lightness 和 Brightness),颜色的明亮程度;

在原理和表现上,HSL 和 HSB 中的 H 完全一致,但是二者的 S 、L 和 B 不一样:

  • HSB 中的 S 控制纯色中混入白色的量,值越白色越少颜色越纯
  • HSB 中的 B 控制纯色中混入黑色的量,值越黑色越少明度越高
  • HSL 中的 S黑白没有关系,饱和度不控制颜色中混入黑白的多与少;
  • HSL 中的 L 控制纯色中的混入的黑白两种颜色

YUV

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

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

YUV的发明是由于彩色电视与黑白电视的过渡时期。 YUV最大的优点在于只需占用极少的带宽

  • 4:4:4 表示完全取样;
  • 4:2:2 表示2:1的水平取样,垂直完全采样;
  • 4:2:0 表示2:1的水平取样,垂直2:1采样;
  • 4:1:1 表示4:1的水平取样,垂直完全采样;
代码语言:javascript
复制
import cv2
from pandas import DataFrame

dog = cv2.imread('./doge.jpg')

用每一个像素创建DataFrame

df = DataFrame(dog.reshape(11,3))

计算有多少个不同的颜色

df.shape[0] - df.duplicated().sum()

3.2 颜色空间的转化

  • cvtColor(img, colorspace):颜色转换
代码语言:javascript
复制
import cv2

def callback(value):
pass

cv2.namedWindow('color',cv2.WINDOW_NORMAL)
cv2.resizeWindow('mouse', 640, 480)

常见的颜色空间

colorspaces = [cv2.COLOR_BGR2RGBA, cv2.COLOR_BGR2BGRA, cv2.COLOR_BGR2GRAY, cv2.COLOR_BGR2HSV, cv2.COLOR_BGR2YUV]
cv2.createTrackbar('curcolor', 'color', 0, 4, callback)

while True:
index = cv2.getTrackbarPos('curcolor', 'color')

# 颜色空间转换
cvt_img = cv2.cvtColor(img, colorspaces[index])

cv2.imshow('color', cvt_img)
key = cv2.waitKey(10)
if key & 0xFF == ord('q'):
	break

cv2.destroyAllWindows()

3.3 Mat的深浅拷贝

什么是Mat

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

  • Mat 由 header 和 data 组成,header 中记录了图片的维数、大小、数据类型等数据。
代码语言:javascript
复制
import cv2

OpenCV用mat这种数据结构来表示图片

C++中用mat来保存图片,python中把mat转化成了numpy的ndarray

cv2.imshow

numpy.ndarray

type(img)

#render175099124 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render175099124 .error-icon{fill:#552222;}#render175099124 .error-text{fill:#552222;stroke:#552222;}#render175099124 .edge-thickness-normal{stroke-width:2px;}#render175099124 .edge-thickness-thick{stroke-width:3.5px;}#render175099124 .edge-pattern-solid{stroke-dasharray:0;}#render175099124 .edge-pattern-dashed{stroke-dasharray:3;}#render175099124 .edge-pattern-dotted{stroke-dasharray:2;}#render175099124 .marker{fill:#333333;stroke:#333333;}#render175099124 .marker.cross{stroke:#333333;}#render175099124 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render175099124 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#render175099124 .cluster-label text{fill:#333;}#render175099124 .cluster-label span{color:#333;}#render175099124 .label text,#render175099124 span{fill:#333;color:#333;}#render175099124 .node rect,#render175099124 .node circle,#render175099124 .node ellipse,#render175099124 .node polygon,#render175099124 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#render175099124 .node .label{text-align:center;}#render175099124 .node.clickable{cursor:pointer;}#render175099124 .arrowheadPath{fill:#333333;}#render175099124 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#render175099124 .flowchart-link{stroke:#333333;fill:none;}#render175099124 .edgeLabel{background-color:#e8e8e8;text-align:center;}#render175099124 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#render175099124 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#render175099124 .cluster text{fill:#333;}#render175099124 .cluster span{color:#333;}#render175099124 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#render175099124 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}HeaderData

代码语言:javascript
复制
class CV_EXPORTS Mat
{
public:
...
int dims; //维数
int rows, cols; //行列数
uchar *data; //存储数据的指针
int *refcount; //引用计数
...
};

字段

说明

字段

说明

dims

维度

channels

通道数 RGB是3

rows

行数

size

矩阵大小

cols

列数

type

dep+dt+chs CV_8UC3

depth

像素的位深

data

存放数据

ndarray中常见属性:

  1. img.data:<memory at 0x0000020FA8B168B8> 内存位置
  2. img.size:921600 元素的总个数
  3. img.dtype:dtype(‘uint8’) 数据类型
  4. img.shape:(480, 640, 3) 各个维度的形状
  5. img.itemsize:1 不属于mat的属性
  6. img.ndim:纬度,本质为通道数
  7. img.imag:虚拟数组

Mat拷贝

  • Mat共享数据

#render2010523814 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render2010523814 .error-icon{fill:#552222;}#render2010523814 .error-text{fill:#552222;stroke:#552222;}#render2010523814 .edge-thickness-normal{stroke-width:2px;}#render2010523814 .edge-thickness-thick{stroke-width:3.5px;}#render2010523814 .edge-pattern-solid{stroke-dasharray:0;}#render2010523814 .edge-pattern-dashed{stroke-dasharray:3;}#render2010523814 .edge-pattern-dotted{stroke-dasharray:2;}#render2010523814 .marker{fill:#333333;stroke:#333333;}#render2010523814 .marker.cross{stroke:#333333;}#render2010523814 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render2010523814 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#render2010523814 .cluster-label text{fill:#333;}#render2010523814 .cluster-label span{color:#333;}#render2010523814 .label text,#render2010523814 span{fill:#333;color:#333;}#render2010523814 .node rect,#render2010523814 .node circle,#render2010523814 .node ellipse,#render2010523814 .node polygon,#render2010523814 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#render2010523814 .node .label{text-align:center;}#render2010523814 .node.clickable{cursor:pointer;}#render2010523814 .arrowheadPath{fill:#333333;}#render2010523814 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#render2010523814 .flowchart-link{stroke:#333333;fill:none;}#render2010523814 .edgeLabel{background-color:#e8e8e8;text-align:center;}#render2010523814 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#render2010523814 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#render2010523814 .cluster text{fill:#333;}#render2010523814 .cluster span{color:#333;}#render2010523814 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#render2010523814 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}Mat AMat BHeaderDataHeader

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

代码语言:javascript
复制
import cv2
import numpy as np

img = cv2.imread('./doge.jpg')

浅拷贝

img2 = img.view()

深拷贝

img3 = img.copy()

在区域内添加色块

img[10:100, 10:100] = [0, 0, 255]

cv2.imshow('img', np.hstack((img, img2, img3)))

cv2.waitKey(0)
cv2.destroyAllWindows()

Pasted image 20220709101802

访问图像(Mat)的属性

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

代码语言:javascript
复制
import cv2
import numpy as np

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

shape属性中包括了3个信息

高度、长度、通道数

print(img.shape)

(54, 60, 3)

图像占用多大空间

高度 * 长度 * 通道数

print(img.size)

9720

图像中每个元素的位深

print(img.dtype)

uint8

3.4 颜色通道的分离与合并

  • split(mat):分割图像的通道;
  • merge((ch1, ch2, ch3)):融合多个通道;
代码语言:javascript
复制
# 图像的分割与融合

import cv2
import numpy as np

img = np.zeros((480, 640, 3), np.uint8)

分割通道

b, g, r = cv2.split(img)

b[10:100, 10:100] = 255
g[10:100, 10:100] = 255

img2 = cv2.merge((b, g, r))

一维

cv2.imshow('img_1', np.hstack((b, g)))

三维

cv2.imshow('img_2', np.hstack((img, img2)))

cv2.waitKey(0)
cv2.destroyAllWindows()

img_1:

Pasted image 20220709103043

img_2:

Pasted image 20220709102951

3.5 绘制直线

利用OpenCV一共的绘制图形API可以轻松在图像上绘制各种图形,例如:直线、矩形、圆、椭圆等。

  • line(img, pt1, pt2, color, thickness, lineType, shift):绘制直线;
    • img:绘制到哪个图像;
    • pt1、pt2:直线的起止位置;
    • color:直线的颜色,BGR元组;
    • thickness:直线粗细;
    • lineType:线型,取值为-1、4、8、16,默认为8,越小锯齿状越明显;
    • shift:坐标缩放比例,默认1,可不写;
代码语言:javascript
复制
# 绘制直线
import cv2
import numpy as np

img = np.zeros((480, 640, 3), np.uint8)

不同锯齿的红色直线

cv2.line(img, (10,20), (300,400), (0, 0, 255), 5, 4, 1)
cv2.line(img, (340,400), (630,20), (0, 0, 255), 5, 16, 1)

cv2.imshow('draw', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Pasted image 20220709105003

4.1 绘制矩形和圆

  • rectangle(img, pt1, pt2, color, thickness, lineType, shift):绘制矩形;
代码语言:javascript
复制
# 绘制矩形
import cv2
import numpy as np

img = np.zeros((480, 640, 3), np.uint8)

cv2.rectangle(img, (80,100), (380,400), (0, 255, 0), 5, 8)

cv2.imshow('draw', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Pasted image 20220709105439
  • circle(img, center, radius, color[, thickness[, lineType[, shift]]]):绘制圆,中括号为可选参数;
代码语言:javascript
复制
# 绘制圆
import cv2
import numpy as np

img = np.zeros((480, 640, 3), np.uint8)

cv2.circle(img, (320,240), 50, (0, 255, 0))
cv2.circle(img, (400,300), 45, (255, 0, 0), 5, 16)

cv2.imshow('draw', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Pasted image 20220709105908

4.2 绘制椭圆

  • ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]]):绘制椭圆(矩形的最大内切椭圆);
    • center:中心位置;
    • axes:长半轴,短半轴组成的元组;
    • angle:逆时针旋转角度;
    • startAngle:起始角度;
    • endAngle:终止角度;
代码语言:javascript
复制
# 绘制椭圆
import cv2
import numpy as np

img = np.zeros((480, 640, 3), np.uint8)

完整椭圆

cv2.ellipse(img, (320,120), (120, 20), 0, 0, 360, (0, 0, 255), 2, 16)

椭圆弧段

cv2.ellipse(img, (320,240), (100, 50), 0, 0, 240, (0, 255, 0), 2, 16)

斜椭圆

cv2.ellipse(img, (320,360), (80, 30), 30, 0, 360, (255, 0, 0), 2, 16)

cv2.imshow('draw', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Pasted image 20220709110852

4.3 绘制多边形并填充多边形

4.3 绘制多边形并填充

  • polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]):绘制多边形
    • pts:点集的集合,int32位;
    • isClosed:是否闭合;
  • fillPoly(img, pts, color[, lineType[, shift[, offset]]]):填充多边形;
    • color:填充颜色;
代码语言:javascript
复制
# 绘制多边形
import cv2
import numpy as np

img = np.zeros((480, 640, 3), np.uint8)

单点集闭合多边形

pts = np.array([(250,100), (150,280), (280,320), (320,120)], np.int32)
cv2.polylines(img, [pts], True, (255, 255, 255), 2, 16)

单点集填充多边形

pts = np.array([(400,100), (300,280), (430,320), (470,120)], np.int32)
cv2.fillPoly(img, [pts], (255, 0, 0))

cv2.imshow('draw', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

注意:点集的集合可以放多个点集,则需要使用中括号括起来以表示点集的集合,多点集 [pts pts]

Pasted image 20220709113434

4.4 绘制文本及中文文本

4.4 绘制文本及中文文本

  • putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, shift]]]):绘制文本;
    • text:文本内容;
    • org:文本在图片中的左下角坐标;
    • fontFace:字体;
    • fontScale:字体大小;
代码语言:javascript
复制
# 绘制文本
import cv2
import numpy as np

img = np.zeros((480, 640, 3), np.uint8)

cv2.putText(img, 'Hello OpenCV', (50,50), cv2.FONT_HERSHEY_COMPLEX, 2, (255, 255, 255))

cv2.imshow('draw', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Pasted image 20220709121046
  • 使用Pillow转换OpenCV的方法来绘制中文
代码语言:javascript
复制
# 绘制中文

OpenCV没有办法直接绘制中文

使用Pillow包

from PIL import ImageFont, ImageDraw, Image
import cv2
import numpy as np

创建纯白背景

img = np.full((200, 200, 3), fill_value=255, dtype=np.uint8)

导入字体文件

font = ImageFont.truetype('./PingFang.ttc', 15)

创建Pillow图片

img_pil = Image.fromarray(img)
draw = ImageDraw.Draw(img_pil)

利用draw去绘制中文

draw.text((100, 100), '中午好', font=font, fill=(0, 255, 0, 0))

重新变回ndarray

img = np.array(img_pil)
cv2.imshow('pillow', img)

cv2.waitKey(0)
cv2.destroyAllWindows()

Pasted image 20220709122356

4.5 习题讲解

例题:使用OpenCV实现Python程序,程序具有以下功能:

  1. 按下键盘L键,拖动鼠标可以绘制直线;
  2. 按下键盘R键,拖动鼠标可以绘制矩形;
  3. 按下键盘C键,拖动鼠标可以绘制圆形,拖动的长度为半径;
代码语言:javascript
复制
import cv2
import numpy as np

全局标志,判断要画什么类型的图

curshape = 0
startpos = (0,0)

创建背景图

img = np.zeros((480, 640, 3), np.uint8)

坚挺鼠标的行为,必须通过鼠标回调实现

def mouse_callback(event, x, y, flags, userdata):
# 引入全局变量
global curshape, startpos
# 引入非本层的局部变量使用关键字:nonlocal
if event == cv2.EVENT_LBUTTONDOWN:
# 记录起始位置
startpos = (x,y)
elif event == cv2.EVENT_LBUTTONUP:
# 判断要画什么类型的图
if curshape == 0: # 画直线
cv2.line(img, startpos, (x,y), (255,255,255), 3, 16)
elif curshape == 1: # 画矩形
cv2.rectangle(img, startpos, (x,y), (255,255,255), 3, 16)
elif curshape == 2: # 画圆
# 计算半径 转换为整数
r = int(((x - startpos[0]) ** 2 + (y - startpos[1]) ** 2) ** 0.5)
cv2.circle(img, startpos, r, (255,255,255), 2)
else: # 其他按键
print("暂不支持绘制其他图形")

创建窗口

cv2.namedWindow('draw_shape', cv2.WINDOW_NORMAL)

设置鼠标回调函数

cv2.setMouseCallback('draw_shape', mouse_callback)

while True:
cv2.imshow('draw_shape', img)
# 检测案件
key = cv2.waitKey(0)
if key == ord('q'):
break
elif key == ord('l'):
curshape = 0
elif key == ord('r'):
curshape = 1
elif key == ord('c'):
curshape = 2

cv2.destroyAllWindows()

Pasted image 20220709124445