颜色空间、数据结构与绘图
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%(白)。
为什么使用HSV? 方便OpenCV做图像处理,比如根据HUE的值就可以判断背景颜色。
HSL
- Hue:色相
- Saturation:饱和度
- Lightness:亮度
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的水平取样,垂直完全采样;
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):颜色转换
import cv2
def callback(value):
passcv2.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 中记录了图片的维数、大小、数据类型等数据。
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
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中常见属性:
- img.data:<memory at 0x0000020FA8B168B8> 内存位置
- img.size:921600 元素的总个数
- img.dtype:dtype(‘uint8’) 数据类型
- img.shape:(480, 640, 3) 各个维度的形状
- img.itemsize:1 不属于mat的属性
- img.ndim:纬度,本质为通道数
- 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的拷贝。
import cv2
import numpy as npimg = 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()
访问图像(Mat)的属性
OpenCV中的Mat在Python中已经转化为ndarray,通过ndarray的属性可访问Mat图像的属性:
import cv2
import numpy as npimg = 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)):融合多个通道;
# 图像的分割与融合
import cv2
import numpy as npimg = np.zeros((480, 640, 3), np.uint8)
分割通道
b, g, r = cv2.split(img)
b[10:100, 10:100] = 255
g[10:100, 10:100] = 255img2 = 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:
img_2:
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,可不写;
# 绘制直线 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()
4.1 绘制矩形和圆
- rectangle(img, pt1, pt2, color, thickness, lineType, shift):绘制矩形;
# 绘制矩形
import cv2
import numpy as npimg = 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()
- circle(img, center, radius, color[, thickness[, lineType[, shift]]]):绘制圆,中括号为可选参数;
# 绘制圆
import cv2
import numpy as npimg = 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()
4.2 绘制椭圆
- ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]]):绘制椭圆(矩形的最大内切椭圆);
- center:中心位置;
- axes:长半轴,短半轴组成的元组;
- angle:逆时针旋转角度;
- startAngle:起始角度;
- endAngle:终止角度;
# 绘制椭圆
import cv2
import numpy as npimg = 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()
4.3 绘制多边形并填充多边形
4.3 绘制多边形并填充
- polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]):绘制多边形
- pts:点集的集合,int32位;
- isClosed:是否闭合;
- fillPoly(img, pts, color[, lineType[, shift[, offset]]]):填充多边形;
- color:填充颜色;
# 绘制多边形
import cv2
import numpy as npimg = 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]
4.4 绘制文本及中文文本
4.4 绘制文本及中文文本
- putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, shift]]]):绘制文本;
- text:文本内容;
- org:文本在图片中的左下角坐标;
- fontFace:字体;
- fontScale:字体大小;
# 绘制文本
import cv2
import numpy as npimg = 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()
- 使用Pillow转换OpenCV的方法来绘制中文
# 绘制中文
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()
4.5 习题讲解
例题:使用OpenCV实现Python程序,程序具有以下功能:
- 按下键盘L键,拖动鼠标可以绘制直线;
- 按下键盘R键,拖动鼠标可以绘制矩形;
- 按下键盘C键,拖动鼠标可以绘制圆形,拖动的长度为半径;
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()