0%

Python-图像操作

Python 进行图像处理的最佳实践,如:

  • pillow:基础的数字图像处理,操作图像的功能强大,API 简单易用。
  • opencv:Open Source Computer Vision Library,计算机视觉的图像处理经典专用库,广泛应用于工业与科研的图像处理、计算机视觉、模式识别。
  • scikit-image:基于 scipy 的图像处理包
  • imghdr:【标准库】推测图片类型
  • pyzbar:二维码识别
  • qrcode:二维码相关操作
  • muggle-ocr:无脑级验证码识别。准确率还可以。
    • tesseract:光学识别验证码,不太准,还需要安装各种语言包,OUT
  • pyexiv2:操作图片的元数据

概述

图像格式

jpg:
bmp:Windows Bitmap
psd:Adobe Photoshop
png:portable Networks graphics:

png8:8 位索引色位图:最多只能展示 256 种颜色,常用于颜色较为单一的图像
png24:24 位索引色位图:最多可展示 1600 万 + 种颜色,图像颜色丰富、清晰,大小也会相应增加,比较适合像摄影作品之类颜色比较丰富的图片

  • 能提供透明背景,是一种专为网页展示而发明的图片格式。一般用于需要背景透明显示或对图像质量要求较高的网页上。

gif:提供的颜色较少,用在对颜色要求不高的地方,如 logo、按钮、表情,主要应用是动图
元数据:metadata:中介数据:中继数据:描述数据的数据。如用来指示存储位置、历史版本等功能。

  • EXIF:数据相机拍摄照时添加。如相机型号、镜头、曝光、图片尺寸等信息
    • 拍摄信息
    • 拍摄器材(机身、镜头、闪光灯等)
    • 拍摄参数(快门速度、光圈 F 值、ISO 速度、焦距、测光模式等)
    • 图像处理参数(锐化、对比度、饱和度、白平衡等)
    • 图像描述及版权信息
    • GPS 定位数据
    • 缩略图
  • IPTF:人工后期通过软件写入的数据。如图片标题、关键字、说明、作者、版权等信息
  • XMP:一种元数据存储和管理的标准,可将 EXIF、IPTF 或其他的数据按 XMP 统一格式存放在图像文件中。
  • 颜色复杂、不带透明的,用 JPG。
  • 带透明的优先用 PNG。
  • 分辨率不变,格式不变,将图像变小

图像色彩空间/颜色模型

RGB

RGB:Red、Green、Blue:红绿蓝:主动产生颜色光源(显示器)

  • 黑色:0,白色 255:黑色没有光线,没有信息

CMYK

CMYK:Cyan、Magenta、Yellow、Black:青、品红、黄、黑:用于印刷、绘画等

HSI

HSI:Hue、Saturation、Intensity:色调、色饱和度、亮度:用于调整颜色分量

色彩空间 -BGR

色彩空间/颜色空间/颜色模型,如 RGB、HSV、YUV 等

RGB:Red+Green+Blue 颜色空间的三维垂直坐标系表示方法,RGB 三通道
BGR:相机设备厂商的主流表示方法,后来 RGB 成了主流和默认,但是这个底层的顺序却保留下来了,事实上 Windows 下的最常见格式之一 bmp,底层字节的存储顺序还是 BGR

  • RGB:人眼的感受光谱范围为:380~780nm。1931年国际照明委员会(CIE)规定将波长700nm、546.1 nm和435.8nm的单色光作为红绿蓝三原色,又称物理三基色

  • OpenCV 彩色图以 BGR 通道顺序存储的,灰度图只有一个通道

    1
    2
    3
    4
    # BGR 格式转为 RGB 格式
    img_cv2_rgb=img_cv2_bgr[...,::-1]
    # 沿 x 轴反转
    img_flip=img_cv2[::-1,:,:]
  • HSV:颜色空间的圆柱坐标系表示法,相比 RGB 能区分亮度等信息

  • OpenCV 与 PIL 的通道不同,对于推理时的结果可能有零点几个点的差别

色彩空间转换

三个分量(通道):色相(Hue)、饱和度(Saturation)、明度(Value),相比 RGB 能区分亮度等信息

  • 色相:Hue,色调,角度度量,取值范围为 0° ~ 360°,从红色开始按逆时针方向计算,红色为 0°,绿色为 120°,蓝色为 240°。就是说,我用红色还是绿色还是粉色
    有三个分量(通道):
  • 色调 H,用角度度量,取值范围为 0° ~ 360°,从红色开始按逆时针方向计算,红色为 0°,绿色为 120°,蓝色为 240°。就是说,我用红色还是绿色还是粉色
  • 饱和度 S,饱和度高,颜色则深而艳。即,我要用淡粉还是 深粉(就是深一点的粉色)
  • 明度 V,表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射情况或反射反射情况有关。就是说,屏幕是亮一点还是暗一点。
  • 饱和度 S:色彩的纯度,饱和度越低则颜色越黯淡 ( 0<= S < 1),饱和度高,颜色则深而艳。即,我要用淡粉还是 深粉(就是深一点的粉色)
  • 明度 V,即颜色的明暗程度。数值越高越接近白色,数值越低越接近黑色 ( 0 <= V < 1);表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射情况或反射反射情况有关。就是说,屏幕是亮一点还是暗一点
绿青色蓝色品红
060120180240300360

图片元数据

Metadata:元数据,又称中介数据、中继数据,是描述数据的数据,图片元数据类似于文件属性

  • 常见的几种标准有:
  • EXIF(exchangeable image file format, 简称 Exif),是一种可交换图像文件的缩写,是专门为数码相机照片设定的可以记录数码照片的属性信息和拍摄数据。Exif 可以附加于 JPEG、TIFF 等文件中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。
    EXIF 最初是由图本电子工业发展协会在 1996 年制定版本为 1.0。1988 年升级到 2.1,增加了对音频文件的支持。2002 年 3 月发布 2.2 版。
    Window7 操作系统具备对 Exif 的原生支持,通过鼠标右键点击图片 => 属性=> 详细信息标签即可查看 Exif 信息,注意 Exif 信息是可以被任意编辑因此 Exif 信息只能作为参考。
    Exif 记录的元信息非常丰富,主要包含一下几类信息:
    • 拍摄信息
    • 拍摄器材(机身、镜头、闪关灯等)
    • 拍摄参数(快门速度、光圈 F 值、ISO 速度、焦距、测光模式等)
    • 图像处理参数(锐化、对比度、饱和度、白平衡等)
    • 图像描述以及版权信息
    • GPS 定位数据
    • 缩略图

EXIF:通常被数码相机在拍摄照片时自动添加,比如相机型号、镜头、曝光、图片尺寸等信息。
IPTC:比如图片标题、关键字、说明、作者、版权等信息。
XMP:由 Adobe 公司制定标准,以 XML 格式保存。用 PhotoShop 等 Adobe 公司的软件制作的图片通常会携带这种信息。

比较

  • 读图:16384 x 16384 的大图,opencv 1.55s,PIL 1.1s,PIL 胜
  • resize:16384 至 4096:opencv:7ms,transform:300ms

Pillow

pillow:基础的数字图像处理,操作图像的功能强大,API 简单易用。

  • PIL(Python Imaging Library)仅支持到 Python 2.7,加上年久失修,所以诞生了 Pillow
1
pip install Pillow

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from PIL import Image
img = Image.open('test.jpg')
# img=Image.new()
# img=Image.frombytes()

# 显示
img.show() # Windows 与 MacOSX 中会将图片保存为一个临时的 BMP 文件

# 属性
img.format # 图像格式,如 bmp、jpg,若不是从文件打开,则为 None
w,h=img.size # 图像的宽与高
img.width
img.height
img.mode # L 为灰度图,RGM 为真彩图,CMYK 为 pre-press 图像,打不开为 IOError

# 保存
img.save('aa.jpg',format='jpeg') # thumbnail 无返回值

创建

1
2
3
4
5
6
7
8
9
10
11
12
img = np.zeros((10, 10), np.uint8)
# 浅灰色背景
img.fill(200)
cv2.imshow('img', img)
cv2.waitKey(0)


img = np.zeros((10, 10, 3), np.uint8)
# 浅灰色背景
img.fill(200)
cv2.imshow('img', img)
cv2.waitKey(0)

模式转换

  • L = R * 299/1000 + G * 587/1000 + B * 114/1000, 将彩色图片转换为黑白图片. greyscale(‘L’), color(‘RGB’)
1
2
3
4
5
6
7
8
9
10
11
12
img=Image.open('a.png').convert('L')
convert() 是图像实例对象的一个方法,接受一个 mode 参数,用以指定一种色彩模式,mode 的取值可以是如下几种:
· 1 (1-bit pixels, black and white, stored with one pixel per byte)
· L (8-bit pixels, black and white)
· P (8-bit pixels, mapped to any other mode using a colour palette)
· RGB (3x8-bit pixels, true colour)
· RGBA (4x8-bit pixels, true colour with transparency mask)
· CMYK (4x8-bit pixels, colour separation)
· YCbCr (3x8-bit pixels, colour video format)
· I (32-bit signed integer pixels)
· F (32-bit floating point pixels)

像素操作

1
2
3
4
5
6
7
im.getpixel((100,80))  #元组坐标取像素
im.putpixel((100,80),(128,30,120)) #修改像素
m.histogram() #如果图像包含多个通道,则返回所有通道的直方图
>>> im.histogram()[:256] #查看第一个通道的直方图
img.getbands() # 返回当前图像的已有通道(R、G、B)
img.getcolor() # 返回列表,包含图片中所有用到的颜色(可统计图片中颜色出现的像素个数),maxcolors 设定颜色个数的最大值
img.getdata() # 以 sequence-like 形式返回当前图片所有像素点的颜色值

通道操作

1
img.split() # 以一个三元 tuple 将原图分裂为三个通道,

写字

1
2
3
4
5
6
7
from PIL import Image,ImageDraw, ImageFont

im=Image.open('lenna.png')
draw = ImageDraw.Draw(im)
ft = ImageFont.truetype("arial.ttf", 50)
draw.text((30,30), u"test",font = ft, fill = 'blue')
im.show()

滤镜 -filter

  • BLUR
  • DETAL
  • SMOOTH
  • SHARPEN
  • EMBOSS
  • ENHANCE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from PIL import Image, ImageFilter
im = Image.open(‘1.png’)
# 高斯模糊
im.filter(ImageFilter.GaussianBlur)
# 普通模糊
im.filter(ImageFilter.BLUR)
# 边缘增强
im.filter(ImageFilter.EDGE_ENHANCE)
# 找到边缘
im.filter(ImageFilter.FIND_EDGES)
# 浮雕
im.filter(ImageFilter.EMBOSS)
# 轮廓
im.filter(ImageFilter.CONTOUR)
# 锐化
im.filter(ImageFilter.SHARPEN)
# 平滑
im.filter(ImageFilter.SMOOTH)
# 细节
im.filter(ImageFilter.DETAIL)

旋转 -rotate-transpose

1
2
3
4
5
6
7
8
# rotate:旋转,angle 为逆时针方向旋转的角度
im=im.rotate(90) #逆时针 90 度
# transpose:特殊角度的旋转加翻转
im=im.transpose(img.ROTATE_180)
im=im.transpose(Image.FLIP_LEFT_RIGHT)
im=im.transpose(Image.FLIP_TOP_BOTTOM)

Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM, Image.ROTATE_90, Image.ROTATE_180, Image.ROTATE_270, Image.TRANSPOSE.

裁剪

1
2
3
4
box=(120,194,220,294) # left,upper,right,lower
region=im.crop(box)
region=region.transpose(Image.ROTATE_180)
im.paste(region,box)

屏幕截图

1
2
3
4
5
from PIL import ImageGrab
img_pil = ImageGrab.grab((0,0,800,200)) # 截取屏幕指定区域的图像
img_pil = ImageGrab.grab() # 不带参数表示全屏幕截图

img_pil.save(r'./screen.png')

裁剪与粘贴

1
2
3
4
box = (120, 194, 220, 294) #定义裁剪区域
region = im.crop(box) #裁剪
region = region.transpose(Image.ROTATE_180)
im.paste(region,box) #粘贴,可用来打水印

图像缩放

resize(): 修改图片的 size, resample 参数传入采样算法, 一般使用高质量缩放的 Image.LANCZOS 参数

1
im = im.resize((100,100)) #参数表示图像的新尺寸,分别表示宽度和高度

根据代码和代码注释, 这两个函数都是对图片进行缩放, 两者的主要区别如下:

  1. resize() 函数会返回一个 Image 对象, thumbnail() 函数返回 None
  2. resize() 修改后的图片在返回的 Image 中, 而原图片没有被修改; thumbnail() 直接对内存中的原图进行了修改, 但是修改需要保存
  3. resize() 中的 size 参数直接设定了 resize 之后图片的规格, 而 thumbnail() 中的 size 参数则是设定了 x/y 上的最大值. 也就是说, 经过 resize() 处理的图片可能会被拉伸, 而经过 thumbnail() 处理的图片不会被拉伸
  4. thumbnail() 函数内部调用了 resize(), 可以认为 thumbnail() 是对 resize() 的一种封装
    两个函数的基本调用方式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 缩放到 50%:
im.resize((w//2,h//2)).save('aa.jpg','jpeg')
im=im.resize((100,100)) #缩放到新的像素大小
# 另有一个 thumbnail(),封装了 resize,没有返回值,按比例缩小
im.thumbnail((w//2,h//2))


from PIL import Image
# Image.resize usage
with Image.open("test.jpg") as img:
resized = img.resize((new_x, new_y), resample=Image.LANCZOS)
resized.save("resized.jpg", format="jpeg")
# Image.thumbnail usage
with Image.open("test.jpg") as img:
img.thumbnail((max_x, max_y), resample=Image.LANCZOS)
img.save("thumbnail.jpg", format="jpeg")

thumbnail() # 制作当前图像的缩略图

图像增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from PIL import Image
from PIL import ImageEnhance

#原始图像
image = Image.open('lena.jpg')
image.show()

#亮度增强
enh_bri = ImageEnhance.Brightness(image)
brightness = 1.5
image_brightened = enh_bri.enhance(brightness)
image_brightened.show()

#色度增强
enh_col = ImageEnhance.Color(image)
color = 1.5
image_colored = enh_col.enhance(color)
image_colored.show()

#对比度增强
enh_con = ImageEnhance.Contrast(image)
contrast = 1.5
image_contrasted = enh_con.enhance(contrast)
image_contrasted.show()

#锐度增强
enh_sha = ImageEnhance.Sharpness(image)
sharpness = 3.0
image_sharped = enh_sha.enhance(sharpness)
image_sharped.show()

put*(): 各种 put 函数, 用于修改当前图片的一些参数

图像通道分离与合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
r, g, b = im.split()  #将彩色图像分离为同样大小的红、绿、蓝三分量子图
imNew = Image.merge(im.mode, (r,g,b))
12)创建缩略图
im.thumbnail((50, 20)) #参数为缩略图尺寸
im.save('2.jpg') #保存缩略图
13)屏幕截图
from PIL import ImageGrab
im = ImageGrab.grab((0,0,800,200)) #截取屏幕指定区域的图像
im = ImageGrab.grab() #不带参数表示全屏幕截图
14)图像增强
from PIL import ImageFilter
im = im.filter(ImageFilter.DETAIL) #创建滤波器,使用不同的卷积核
im = im.filter(ImageFilter.EDGE_ENHANCE) #边缘增强
im = im.filter(ImageFilter.EDGE_ENHANCE_MORE) #边缘增强
15)图像模糊
im = im.filter(ImageFilter.BLUR)
im = im.filter(ImageFilter.GaussianBlur) #高斯模糊
im.filter(ImageFilter.MedianFilter) #中值滤波
16)图像边缘提取
im = im.filter(ImageFilter.FIND_EDGES)
17)图像点运算
im = im.point(lambda i:i*1.3) #整体变亮
im = im.point(lambda i:i*0.7) #整体变暗
im = im.point(lambda i: i*1.8 if i<100 else i*0.7) #自定义调整图像明暗度
也使用图像增强模块来实现上面类似的功能,例如
from PIL import ImageEnhance
enh = ImageEnhance.Brightness(im)
enh.enhance(1.3).show()
18)图像冷暖色调整
r, g, b = im.split() #分离图像
r = r.point(lambda i:i*1.3) #红色分量变为原来的 1.3 倍
g = g.point(lambda i:i*0.9) #绿色分量变为原来的 0.9
b = b.point(lambda i:0) #把蓝色分量变为 0
im = Image.merge(im.mode,(r,g,b)) #合并图像
im.show()
19)图像对比度增强
from PIL import ImageEnhance
im = ImageEnhance.Contrast(im)
im = im.enhance(1.3) #对比度增强为原来的 1.3 倍

获取文字的像素宽度

1
2
3
4
5
from PIL import ImageFont

font = ImageFont.truetype(r"fonts/SOURCEHANSANSCN-REGULAR.OTF", 14)
sample = "test"
print(font.getlength(sample))

OpenCV

opencv:Open Source Computer Vision Library,计算机视觉的图像处理经典专用库,广泛应用于工业与科研的图像处理、计算机视觉、模式识别。

  • 具有 C++、Python 接口,Opencv-Python 调用了 C/C++,易用 + 运行效率兼得。
  • 高效计算,多线程
  • 可移植性,跨平台
  • 开源,BSD 协议
  • 运行速度:
    • 加载一张本地图像约 10ms

常识

组成

从下至上

  • OpenCV HAL 硬件加速层:SSE, NEON, IPP, OpenCL, CUDA, OpenCV4Tegra
  • opencv_contrib:大多数高层级的函数功能
  • 语言绑定和示例应用程序
  • 与操作系统的交互

模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
opencv2
├── calib3d
├── core
│ └── cuda
│ └── detail
├── features2d
├── flann
├── hal
├── highgui
├── imgcodecs
├── imgproc
├── ml
├── objdetectw
├── photo
├── shape
├── stitching
│ └── detail
├── superres
├── video
├── videoio
└── videostab

#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core.hpp>
using namespace cv;
  • Core
    • 基本数据结构与基本操作、动态数据结构
    • 绘图函数
    • 数组操作相关函数
    • 辅助功能与系统函数及宏
    • 与 OpenGL 的互操作
  • Improc:Image+Processing 图像处理模块,基本的图像转换、滤波和卷积等操作
    • 线性和非线性的图像滤波
    • 图像的几何变换
    • 其它(Miscellaneous)图像转换
    • 直方图相关
    • 结构分析和形状描述
    • 运动分析和对象跟踪
    • 特征检测
    • 目标检测等内容
  • HigGUI:显示图像及具有简单输入的用户交互函数,轻量级的 Windows UI 工具包
    • 也就是 high gui,高层 GUI 图形用户界面,包含媒体的 I / O 输入输出,视频捕捉、图像和视频的编码解码、图形交互界面的接口等内容
  • Video:读写视频流
    • 视频分析组件,该模块包括运动估计,背景分离,对象跟踪等视频处理相关内容。
  • Calib3d:Calibration+3D 校准单个、双目及多个相机的算法实现,多视角几何算法,单个立体摄像头标定,物体姿态估计,立体相似性算法,3D 信息的重建等等
  • Feature2d:检测、描述及匹配特征点的算法
    • 特征检测和描述
      特征检测器(Feature Detectors)通用接口
      描述符提取器(Descriptor Extractors)通用接口
      描述符匹配器(Descriptor Matchers)通用接口
      通用描述符(Generic Descriptor)匹配器通用接口
      关键点绘制函数和匹配功能绘制函数
  • Objdectect:特定目标检测,如人脸或行人
  • Ml:机器学习算法模块,能与 OpenCV 的数据类型良好交互、
    • Machine Learning,机器学习模块, 基本上是统计模型和分类算法
  • Flann:最邻近搜索, Fast Library for Approximate Nearest Neighbors,高维的近似近邻快速搜索算法库
  • GPU:由多个 cuda* 模块组成,实现 GPU 的加速
  • stitching:图像拼接
  • Contribopencv_contrib,扩展模块,一些新的、没有被集成到 OpnCV 库的东西
  • Legacy:一些已经废弃的代码库,保留下来作为向下兼容
  • Ocl:开放并行编程的 Khronos OpenCL 标准,目标是提供可以运行在任何 GPU 或其他可以搭载 Khronos 的并行设备。
  • Dnn深度神经网络
  • Face:人脸识别
  • Text:文本检测及识别,基于许多开源的 OCR 算法
  • Rgbd:处理由 Kinect 或其他深度传感器获得的 RGB+ 深度图像
  • Bioinspired:基于生物学启发的视觉技术
  • ximgpro 和 xphoto:先进的图像处理以及计算摄影学方法
  • Tacking:现代目标追踪算法

不用

  • Nonfree:专利保护或受限的算法如 SIFT 算法、SURF 算法,需进行特殊操作,才能进行商业使用
    • Cmake 编译时勾选“OPENCV_ENABLE_NONFREE”
      • SIFT implementation.
        The class implements SIFT algorithm by D. Lowe.
        2,SURF implementation.
        The class implements SURF algorithm by H. Bay et al.

基本操作

1
2
3
4
pip install opencv-python
# Successfully installed numpy-1.20.1 opencv-python-4.5.1.48

import cv2

对象属性

Attention:OpenCV 除涉及到 numpy 的,其他都是 (宽度,高度)的格式,因 numpy、pytorch 这种是由于本身张量或者矩阵形式的维度相关,先行后列,高度在前、宽度在后

1
2
3
4
5
6
7
8
9
10

# 三通道图:(高,宽,通道)
img_cv2.shape # Attention: opencv是反常识的:高/行数 宽/列数 通道数,然而`cv2.resize()`,`cv2.warpAffine()`函数的`dst`和`dsize`参数输入是 `(宽度,高度)`,与`img.shape`相反。

# shape=[B,C,H,W]
torch_tensor.shape # torch.size(32,1,1024,4096),batch_size:32,图像通道数:1,图像高:1024,图像宽:144


img_cv2.size # 像素数目
img_cv2.dtype # 图像的数据类型

读入 - 显示 - 保存

  • Attention:即使图像路径错误,它不会引发任何错误,只是 img_cv2 为 None
1
2
3
4
5
6
7
8
9
10
img_cv2 = cv2.imread( # 图像矩阵,numpy 的 ndarray
file_path, # str: 图像路径
flags) # int: 读入的模式,通道数和位深的参数

# flags
cv2.IMREAD_UNCHANGED = -1 # 不进行转化,anycolor+anydepth,RGBA 的具有 Alpha 的图像比如保存为了 16 位的图片,读取出来仍然为 16 位。但 8 位的灰度图会被读成 3 通道的 BGR 图,三个通道的值相等
cv2.IMREAD_GRAYSCALE = 0 # 转换为单通道灰度图,深度为 8 位
cv2.IMREAD_COLOR = 1 # 转换为 BGR 彩色三通道图像,图像深度转为 8 位,对于 4 通道的 RGBA,Alpha 会被剥离
cv2.IMREAD_ANYDEPTH = 2 # 保持图像深度不变,进行转化为灰度图。
cv2.IMREAD_ANYCOLOR = 4 # 通道数由文件实际通道数(不超过 3,大于 3 则只取前三个通道),8 位
  • 显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cv2.namedWindow(
win_name, # str,窗口名
cv2.WINDOW_AUTOSIZE) # 默认,表示窗口大小自适应图片
cv2.WINDOW_NORMAL,表示窗口大小可调

None =cv2.imshow(
win_name, # str,窗口名称
img_cv2) # 图像的 numpy 格式

retval=cv2.waiKey( # retval:按键键码
[delay] # delay:毫秒级延时
)

None=cv2.destroyAllWindows()
# 关掉所有窗口,释放资源

img_cv2 = cv2.imread(i)
cv2.namedWindow('img_show', cv2.WINDOW_GUI_NORMAL)
cv2.imshow('img_show', img_cv2)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • 保存
1
cv2.imwrite('test.png',img_output,[cv2.IMWRITE_PNG_COMPRESSION,0])

文件格式由 filename 参数的文件扩展名确定

  • 推荐使用 PNG,无损压缩
  • BMP 无损不压缩,很大
  • JPEG 有损压缩会丢失信息
  • params:
  • JPEG:图像的质量,0-100,越大质量越高,文件越大,默认 95
  • PNG:压缩级别,0-9,越大文件越小,压缩用时越长,默认为 3
  • PPM,PGM 或 PBM:1 表示文件是以二进制,0 表示文件是以纯文本存储,默认为 1

像素操作

  • 尽量使用 numpy 这一优化过的矩阵运算,而不是像素的循环。
1
2
3
4
5
6
7
8
9
10
11
12

img_cv2[100,100] # 根据坐标取像素
img_cv2[100,100,2] # 第三个通道即 R 通道的值
img_cv2[y,x,通道]

img_cv2[101,101]=[255,255,255]

print(img_cv2.item(10,10,2))
img_cv2.itemset((10,10,2),100)
print(img_cv2.item(10,10,2))

roi=img_cv2[20:30,30:50] # numpy 索引取 ROI

通道处理

1
2
3
4
5
6
7
8
9
10
11
12
13
r,g,b=cv2.split(img) # 拆分
img=cv2.merge(r,g,b) # 合并
b=img[:,:,0] # 拆分 blue 通道
img_blue = img_cv2[:, :, 0].copy() # 取单通道图像
img_green = img_cv2[:, :, 1].copy()
img_red = img_cv2[:, :, 2].copy()
img[:,:,2]=0 # 令所有 red 通道值为 0

# Gray Scale
img_out=0.2126*img_red+0.7152*img_green+0.0722*img_blue
# luint8 数据类型,像素中每个通道取值为 0-255
img_out=img_out.astype(np.uint8)

解码

1
2
# 慢慢慢,内存操作怎么会这么慢,查资料说它会先将内存数据写到临时文件再读取...laji 啊
cv2.imdecode()

裁剪

裁剪:考虑原图像的宽高干扰,常在将样本输入网络前统一大小,

blur

filter

contrast

填充

1
2
cv2.copyMakeBorder(src, top, bottom, left, right, borderType[, dst[, value]])
im = cv2.copyMakeBorder(im, 80, 80, 80, 80, cv2.BORDER_CONSTANT, value=(255, 255, 255))
  • src: 原图片
  • top: 上方填充宽度
  • bottom: 下方填充宽度
  • left: 左边填充宽度
  • right: 右边填充宽度
  • borderType: 边界类型
  • value: 边界填充值

缩放

缩放:向外缩放与向内缩放

1
2
3

img_size=img_cv2.shape # 宽/行数 长/列数 通道数
new_img=cv2.resize(img_cv2,(int(img_size[1]*0.5),int(img_size[0]*0.5)),cv2.INTER_LINEAR)
  • INTER_NEAREST:最近邻插值
  • INTER_LINEAR:双线性插值,默认使用
  • INTER_AREA:使用像素区域关系进行重采样,图像缩小时优先使用
  • INTER_CUBIC:4x4 的邻域上进行双立方插值,图像放大时优先使用

翻转与旋转

翻转:将图像进行镜像操作,包括水平镜像翻转、垂直镜像翻转、原点镜像翻转

旋转:沿画面的中心进行任意角度的变换,通过将原图像与仿射变换矩阵相乘实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 翻转
cv2.flip(img_cv2,1)
# 0,垂直翻转,x 轴
# 1,水平反转,y 轴
# -1,水平垂直翻转

# 旋转
img_rotate=np.rot90(img_cv2,3) # 逆时针旋转3次90度



cv2.getRotationMatrix2D
该函数的第 1 个参数是旋转中心,第 2 个参数是逆时针旋转角度,第 3 个参数是缩放倍数,对于只是旋转的情况参数值是 1,返回的值就是做仿射变换的矩阵。然后通过 cv2.warpAffine()将原图像矩阵乘以旋转矩阵得到最终的结果。


# 去除黑边的操作
crop_image = lambda img, x0, y0, w, h: img[y0:y0+h, x0:x0+w] # 定义裁切函数,后续裁切黑边使用

def rotate_image(img, angle, crop):
"""
angle: 旋转的角度
crop: 是否需要进行裁剪,布尔向量
"""
w, h = img.shape[:2]
# 旋转角度的周期是 360°
angle %= 360
# 计算仿射变换矩阵
M_rotation = cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1)
# 得到旋转后的图像
img_rotated = cv2.warpAffine(img, M_rotation, (w, h))

# 如果需要去除黑边
if crop:
# 裁剪角度的等效周期是 180°
angle_crop = angle % 180
if angle > 90:
angle_crop = 180 - angle_crop
# 转化角度为弧度
theta = angle_crop * np.pi / 180
# 计算高宽比
hw_ratio = float(h) / float(w)
# 计算裁剪边长系数的分子项
tan_theta = np.tan(theta)
numerator = np.cos(theta) + np.sin(theta) * np.tan(theta)

# 计算分母中和高宽比相关的项
r = hw_ratio if h > w else 1 / hw_ratio
# 计算分母项
denominator = r * tan_theta + 1
# 最终的边长系数
crop_mult = numerator / denominator

# 得到裁剪区域
w_crop = int(crop_mult * w)
h_crop = int(crop_mult * h)
x0 = int((w - w_crop) / 2)
y0 = int((h - h_crop) / 2)
img_rotated = crop_image(img_rotated, x0, y0, w_crop, h_crop)
return img_rotated
#水平镜像
h_flip = cv2.flip(img,1)
#垂直镜像
v_flip = cv2.flip(img,0)
#水平垂直镜像
hv_flip = cv2.flip(img,-1)
#90 度旋转
rows, cols, _ = img.shape
M = cv2.getRotationMatrix2D((cols/2, rows/2), 45, 1)
rotation_45 = cv2.warpAffine(img, M, (cols, rows))
#45 度旋转
M = cv2.getRotationMatrix2D((cols/2, rows/2), 135, 2)
rotation_135 = cv2.warpAffine(img, M,(cols, rows))
#去黑边旋转 45 度
image_rotated = rotate_image(img, 45, True)

#显示
plt.figure(figsize=(15, 10))
plt.subplot(2,3,1), plt.imshow(img)
plt.axis('off'); plt.title('原图')
plt.subplot(2,3,2), plt.imshow(h_flip)
plt.axis('off'); plt.title('水平镜像')
plt.subplot(2,3,3), plt.imshow(v_flip)
plt.axis('off'); plt.title('垂直镜像')
plt.subplot(2,3,4), plt.imshow(hv_flip)
plt.axis('off'); plt.title('水平垂直镜像')
plt.subplot(2,3,5), plt.imshow(rotation_45)
plt.axis('off'); plt.title('旋转 45 度')
plt.subplot(2,3,6), plt.imshow(image_rotated)
plt.axis('off'); plt.title('去黑边旋转 45 度')
plt.show()

拼接

1
2
img_new=cv2.hconcat([img_1,img_2,img_3]) # 水平拼接
img_new=cv2.vconcat([img_1,img_2,img_3]) # 垂直拼接

裁剪

1
new_img_cv2=img_gray_cv2[y1:y2,x1:x2]

平移

1
2
3
4
5
6
7
# 放射变换来平移
img_gray=cv2.cvtColor(img_cv2,cv2.COLOR_BGR2GRAY)
M = np.float32([[1, 0, 100],
[0, 1, 50]])
rows,cols = img_gray.shape
img_warp=cv2.warpAffine(img_cv2,M,(cols,rows))
st.image(img_warp, width=ST_IMAGE_WIDTH)

二值化 threshold

1
2
3
4
5
6
7
8
9
10
11
12
cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
retval,img_hreshold=cv2.threshold(
img_gray,
10, # thresh,阈值
255, # maxval,
cv2.THRESH_BINARY)
cv2.THRESH_BINARY =0, // value > thresh ? maxval : 0
cv2.THRESH_BINARY_INV =1, // value > thresh ? 0 : maxval
cv2.THRESH_TRUNC =2, // 截取下 value > thresh ? thresh : value
cv2.THRESH_TOZERO =3, // value > thresh ? value : 0
cv2.THRESH_TOZERO_INV =4, // value < thresh ? value : 0
cv2.THRESH_OTSU =8 // use Otsu algorithm to choose the optimal threshold value; combine the flag with one of the above CV_THRESH_* values */

边缘检测

  • canny 边缘检测:检测轮廓边界像素
1
img_out = cv2.Canny(img, 30, 200)

轮廓

轮廓:连续的点,边界像素合成轮廓曲线,常用于形状分析、物体检测与识别

轮廓查找

  • 寻找轮廓是针对白色物体的,即需要物体是白色,背景是黑色。不然会找到图片最外面的一个框。
1
2
3
4
5
6
7
8
# 灰度图
img_gray = cv2.cvtColor(img_cv2,cv2.COLOR_BGR2GRAY)
# 二值化图
ret, img_bin = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy = cv2.findContours( # 提取轮廓,
img_threshold, # 二值化图像
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
  • mode
    • RETR_EXTERNAL:只检测最外层轮廓
    • RETR_LIST:检索所有轮廓并保存到表 List 中,检测的轮廓不建立等级关系
    • RETR_CCOMP 检索所有轮廓组织成两层结构,一层为所有外边界,一层为孔的边界如果内孔内还有一个连通物体,这个物体的边界也在顶层。
    • RETR_TREE 检索所有轮廓,并建立等级树结构的轮廓。
  • Method:为轮廓的近似办法
    • CHAIN_APPROX_NONE:将轮廓编码中的所有的点存储为轮廓点,相邻的两个点的像素位置差不超过 1,即 max(abs(x1-x2),abs(y2-y1))==1
    • CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需 4 个点来保存轮廓信息
    • CHAIN_APPROX_TC89_L1,CHAIN_APPROX_TC89_KCOS 使用 teh-Chinl 链逼近算法,用于减少返回的点数
  • Offset:表示代表轮廓点的偏移量,可以设置为任意值。

绘制轮廓

1
2
3
4
5
6
7
8
9
10
11
# 绘制找到的轮廓
c2.drawContours(img_cv2, contours,-1,(0,0,255),2)
cv2.drawContours(img_cv2, # 待绘制的图像
contours, # 轮廓组
contourIdx, # 指定要绘制轮廓的编号,负数则绘制所有的轮廓
color,
thickness=None, # 线的粗细,负数则填充轮廓内部
lineType=None, # 线的连通性
hierarchy=None, # 层次结构信息
maxLevel=None, #
offset=None) # 按照偏移量移动所有的轮廓(点坐标)

轮廓特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
image,contours,hierarchy=cv2.findContours(thresh,1,2)
cnt=contours[0]

# 特征矩
M=cv2.moments(cnt)
print(M)

# 重心/质心
cx=int(M['m10']/M['m00'])
cy=int(M['m01']/M['m00'])

# 轮廓面积
area=cv2.contourArea(cnt)

# 轮廓周长
perimeter = cv2.arcLength(cnt,True) # True:形状闭合,False:一条曲线

# 近似多边形
epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)

2.7 边界矩形
包围对象的垂直边界矩形,一个直矩形,没有旋转。不会考虑对象是否旋转。所以边界矩形的面积不是最小的。可以使用函数 cv2.boundingRect()查找得到
cv::Rect r0= cv::boundingRect(cv::Mat(contours[0]));
cv::rectangle(result,r0,cv::Scalar(0),2);

#(x,y)为矩形左上角的坐标,(w,h)是矩形的宽和高
x,y,w,h=cv2.boundingRect(cnt)
img=cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
# 旋转的边界矩形,这个边界矩形是面积最小的,因为它考虑了对象的旋转。用函数 cv2.minAreaRect()。返回的是一个 Box2D 结构,其中包含矩形最上角角点坐标(x,y)矩形的宽和高(w,h)以及旋转角度。但是要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv2.boxPoints()获得。

# 最小外接圆:所有能包括对象的圆中面积最小的一个
(x,y),radius=cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
# 椭圆拟合
ellipse = cv2.fitEllipse(cnt)
img = cv2.ellipse(img,ellipse,(0,255,0),2)

Blob 分析

Blob 分析:Blob Analysis:对图像中相同像素的连通域进行分析,该连通域称为 Blob。常对二值化后的图像进行分析。

  • 从背景分离目标,计算目标数量、位置、形状、方向、大小。
  • 一般来说,基于块的分析比基于像素的分析快得多。

形态学操作

  • 用处:
    • 消除噪声;
    • 分割出独立的图像元素,在图像中连接相邻的元素;
    • 寻找图像中明显的极大值区域或极小值区域;
    • 求出图像的梯度。

膨胀:dilate:卷积操作,将目标像素的值替换为卷积核覆盖区域的 “ 局部最大值”

  • 作用:扩张明亮区域,填充凹面,发现连通分支 (“颜色”块)

腐蚀:erode:卷积操作,与膨胀相反,将目标像素的值替换为卷积核覆盖区域的“局部最小值”

  • 作用:使边界向内部收缩,缩减明亮区域,消除小且无意义的特征,如突起、斑点状的噪声,

形态学:morphology:
开运算:MOP_OPN:Opening Operation:先腐蚀后膨胀

  • 用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积.

闭运算:MOP_CLOSE:Closing Operation:先膨胀后腐蚀

  • 用来填充物体内细小空洞、连接邻近物体、平滑其边界的同时并不明显改变其面积.

形态学梯度:MorphologicalGradient:图像的膨胀与腐蚀的差别

  • dst=dilate(src,element)-erode(src,element)
  • 标注明亮边缘

顶帽/礼帽:TopHat:原始图像与进行开运算之后得到的图像的差。

  • dst=src-open(src,element)
  • 原图像的减去开运算后的图,
  • 突出了与邻域相比更亮的部分
  • 与核的大小相关

黑帽/底帽:BlackHat:进行闭运算之后得到的图像与原始图像的差。

  • dst=close(src,element)-src
  • 闭运算 - 原图像
  • 显示与邻域相比更暗的部分
  • 分离比邻近点暗一些的斑块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 读入图像:
img_gray = cv2.cvtColor(img_cv2, cv2.COLOR_BGR2GRAY)
kernel = np.ones((10, 10), np.uint8)
# 腐蚀
img_erosion = cv2.erode(img_gray, kernel, iterations=1)

# 膨胀
img_dilation = cv2.dilate(img_gray, kernel, iterations=1)

# 开运算
img_open = cv2.morphologyEx(img_gray, cv2.MORPH_OPEN, kernel)

# 闭运算
img_close = cv2.morphologyEx(img_gray, cv2.MORPH_CLOSE, kernel)

# 形态学梯度
img_gradient = cv2.morphologyEx(img_gray, cv2.MORPH_GRADIENT, kernel)

# 顶帽/礼帽
img_tophat = cv2.morphologyEx(img_gray, cv2.MORPH_TOPHAT, kernel)

# 底帽/黑帽
img_blackhat = cv2.morphologyEx(img_gray, cv2.MORPH_BLACKHAT, kernel)


滤波

滤波:增强需要的特征或去除图像中不需要的干扰。

低通滤波器:LPF: 有助于消除噪声,模糊图像等.
高通滤波器:HPF:有助于找到边缘

均值滤波:用像素点周围像素的平均值代替像素值,在滤除噪声的同时也会滤掉图像的边缘信息

1
2
3
img_mean = cv2.blur(img_cv2,(5,5))

# 另外还有 boxFilter,相比 blur 不进行归一化操作,用得少

高斯滤波:相比于均值滤波简单的取平均值,这里是求加权平均,距离越近的点权重越大,距离越远的点权重越小。

  • 去除高斯噪声
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
img_guassian = cv2.GaussianBlur(img_cv2,(5,5),0)


def GaussianBlur(
src,
ksize, # 高斯核的大小,(width,height),两者为正奇数,若设为 0,可根据 sigma 得到
sigmaX, # X 方向的高斯核标准差
dst=None,
sigmaY=None, # Y 方向的高斯核标准差,若为 None,则与 sigmaX 相等
boderType=None
)
img_nlur=cv2.GaussianBlur(img_cv2,
(5,5), # 高斯矩阵的长与宽
0) # 标准差
img_nlur=cv2.GaussianBlur(img_cv2, (5, 5), 1, 0)

中值滤波:像素周围邻域像素集中的中值代替原像素

  • 用于去除椒盐噪声和斑块噪声
1
img_median = cv2.medianBlur(img_cv2,5)

双边滤波:

  • 两个滤波器:
    • 一个空间上的高斯滤波器:使临近的像素点被用于滤波
    • 一个基于像素差的高斯滤波器:使与中间像素值相同的像素点被用于滤波
  • 在滤波的同时能保证一定的边缘信息
1
img_bilater=cv2.bilateralFilter(img,9,75,75)

卷积

卷积:convolution:卷积核按步长对图像局部像素块进行加权求和的过程

核:kernel:实质上是一个固定大小的权重数组,该数组中的锚点通常位于中心。通常情况下,选取卷积核大小为 1x1,3x3,5x5,7x7 等

利用像素点和其邻域像素之前的空间关系,通过加权求和的操作,实现模糊(blurring),锐化(sharpening),边缘检测(edge detection)等功能

取奇数大小的目的是为了设置卷积核中心为锚点,方便卷积核和图像的对齐处理

设置卷积核大小对称的目的是为了在空间域中充分利用像素点和其领域像素间的关系。当然这不是必须的,如果需要针对某一轴进行处理,可以设置 1x3 或 3x1 大小。

检测拟合

圆检测

1
2
3
4
5
6
7
8
9
10
# 单通道图内检测圆形物体
cv2.HoughCircles(img_cv2, # 单通道图像
method, # 检测方法,目前唯一实现:cv2.HOUGH_GRADIENT,
dp, # 累加器分辨率与图像分辨率的反比。dp 获取越大,累加器数组越小。如 1
minDist, # 圆心之间的最小距离(如果距离太小,会产生很多相交的圆,如果距离太大,则会漏掉正确的圆)检测到的圆的中心,(x,y)坐标之间的最小距离,如 20,只需要一个圆的话,就设图像的边长。
corcles=None, # Canny 边缘函数的高阈值
param1=None, # 用于处理边缘检测的梯度值方法,50,
param2=None, # cv2.HOUGH_GRADIENT 方法的累加器阈值。阈值越小,检测到的圆越多,30,
minRadius=None, # 目标圆的最小半径
maxRadius=None) # 目标圆的最大半径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# demo
import cv2
import numpy as np

img_gray = cv2.cvtColor(img_cv2, cv2.COLOR_BGR2GRAY)
img_gray = cv2.medianBlur(img_gray, 5)

rst, th1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, 5)
th3 = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, 5)


kernel = np.ones((5, 5), np.uint8)
erosion = cv2.erode(th2, kernel, iterations=1)
dilation = cv2.dilate(erosion, kernel, iterations=1)

img_canny = cv2.Canny(erosion, 30, 100)

circles = cv2.HoughCircles(img_canny, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=30, minRadius=20, maxRadius=40)

circles = np.uint16(np.around(circles))
for i in circles[0, :]:
# draw the outer circle
cv2.circle(img_cv2, (i[0], i[1]), i[2], (0, 255, 0), 2)
# draw the center of the circle
cv2.circle(img_cv2, (i[0], i[1]), 2, (0, 0, 255), 3)
print(len(circles[0, :]))

直线拟合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cv2.HoughLines():二值图像中查找直线
cv2.HoughLinesP():统计概率霍夫线变换函数,可查找直线段
HoughLinesP(
image, # 单通道 8 位二值图像,常用 canny 边缘检测的结果图像
rho, # 以像素为单位的距离精度,double,常用 1.0
theta, # 线段以弧度为单位的角度精度,推荐用 numpy.pi/180 
threshold, # 累加平面的阈值参数,int 类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用 100 试试
lines=None, # 这个参数的意义未知,发现不同的 lines 对结果没影响,但是不要忽略了它的存在 
minLineLength=None, # :线段以像素为单位的最小长度,根据应用场景设置 
maxLineGap=None # 同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段

返回检测到的直线的端点

threshold 参数表示检测一条直线所需最少的曲线交点。
lines 参数表示储存着检测到的直线的参数对  的容器,也就是线段两个端点的坐标。
minLineLength 参数表示能组成一条直线的最少点的数量,点数量不足的直线将被抛弃。
maxLineGap 参数表示能被认为在一条直线上的亮点的最大距离。
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, 100, 10)

距离类型

  • cv::DIST_L2:最小二乘法:
  • cv::DIST_L1
  • cv::DIST_L12
  • cv::DIST_FAIR
  • cv::DIST_WELSCH
  • cv::DIST_HUBER

绘制

1
2
3
1920*1080 的屏幕图
取右下角点的像素值: img_cv2[1079,1919]
在右下角点上画圆: cv2.circle(img_cv2,(1920,1080),10,(0,0,255),-1)
1
2
3
4
# 生成空图像
img_cv2 = np.zeros((1944, 2592), dtype=np.uint8)
img_cv2[:, :] = 255
img_cv2 = cv2.cvtColor(img_cv2, cv2.COLOR_GRAY2BGR)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 直线
cv2.line(img_cv2,(0,0),(1260,1260),(255,0,0),5)
cv2.line(img_cv2,
pt1, # 线段起始点
pt2, # 线段终止点
color # BGR 颜色
[thickness, # 线宽度,粗细
lineType, # 线型,cv.FILLED,等
shift]
)

# 矩形
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.rectangle(img_cv2,(350,0),(500,128),(0,255,0),3)
cv2.rectangle(img_cv2,
pt1, # 矩形左上角
pt2, # 矩形右下角
color
[, thickness
[, lineType
[, shift]]])
# 圆
cv2.circle(img_cv2,(425,63),63,(0,0,255),-1) # 圆,-1 为向内填充
cv2.circle(img,
center, # center:tuple,圆心
radius, # radius:半径
color
[, thickness
[, lineType
[, shift]]])

# 椭圆
# 中心位轩,轴长度,椭圆沿逆时针方向旋转的角度。startAngle 和 endAngle 表示从主轴沿顺时针方向测量的椭圆弧的开始和结束。即给出 0 和 360 给出完整的椭圆。
cv2.ellipse(img_cv2,(256,256),(100,50),0,0,360,255,-1)

# 多边形
pts = np.array([[100, 20], [200, 300], [700, 200], [500, 150], [500, 100]], np.int32)
pts = pts.reshape((-1,1,2))
# 注意第三个参数若是 False,我们得到的是不闭合的折线
cv2.polylines(img_cv2, [pts], True, (0, 255, 255), thickness=3)

# 文字
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'OpenCV',(10,500), font, 4,(255,255,255),2,cv2.LINE_AA)
cv2.putText(img, # 画布
text, # str,文字
org, # 文本框左下角坐标
fontFace, # 字体,具体字体可查 API,FONT_HERSHEY_SIMPLEX, FONT_HERSHEY_PLAIN, FONT_HERSHEY_DUPLEX,FONT_HERSHEY_COMPLEX, FONT_HERSHEY_TRIPLEX, FONT_HERSHEY_COMPLEX_SMALL, FONT_HERSHEY_SCRIPT_SIMPLEX, orFONT_HERSHEY_SCRIPT_COMPLEX, 以上所有类型都可以配合 FONT_HERSHEY_ITALIC 使用,产生斜体效果
fontScale, # 1,缩放倍数,尺寸因子,值越大文字越大,该值和基础大小相乘得到字体大小
color # 如 (0, 0, 255) 红色,BGR
[, thickness[, lineType[, bottomLeftOrigin]]])

视频读写

1
2
3
4
5
6
# 读视频类:可从视频文件、摄像头中读取图像。
cv2.VideoCapture() # 视频流

# 写视频类:要在创建视频 时设置一系列参数,包括:文件名,编解码器,帧率,宽度和高度等。
fourcc = cv2.VideoWriter_fourcc(*'XVID') # 编解码器:MJPG、XVID、DIVX
cv2.VideoWriter('output.avi',fourcc, 20.0, (640,480))

图像归一化

将矩阵的值变换到某个区间内

  • 如 cv2.imshow 可以正常显示,但保存或显示到网页上为全黑的,是由于图像的格式在 float32 与 uint8 乱了,需要
1
2
3
4
5
6
7
8
9
img = cv2.normalize(img, None, 0  , 255, cv2.NORM_MINMAX, cv2.CV_8U)

cv2.normalize(src[, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]]]) → dst
src:输入数组
dst:与 src 相同大小的输出数组
alpha:范数值,下限
beta:上限
norm_type:类型
mask:
  • norm_type
    • NORM_MINMAX:线性归一化,平移或缩放到一个指定的范围
    • $dst(i,j)=\dfrac{[src(i,j)-min(src(x,y))]*(max-min)}{max(src(x,y)-min(src(x,y)))}+min$
    • NORM_INF:L 无穷范数,即矩阵各元素绝对值的最大值(切比雪夫距离)
      • 除以数组中绝对值最大值
      • $dst(i,j)=\dfrac{src(i,j)}{Max|src(x,y)}$
    • NORM_L1:L1 范数,即矩阵元素的绝对值之和(曼哈顿距离)
      • 除以数组和的绝对值
      • $dst(i,j)=\dfrac{src(i,j)}{|\sum src(x,y)|}$
    • NORM_L2:分母为 L2- 范数,即矩阵各元素的欧几里德距离之和
      • 除以数组平方和再开根号
      • $dst(i,j)=\dfrac{src(i,j)}{\sqrt{\sum {src(x,y)^2}}}$

norm_type 有 NORM_INF, NORM_MINMAX,NORM_L1 和 NORM_L2 四种。
1、在 NORM_MINMAX 模式下,alpha 表示归一化后的最小值,beta 表示归一化后的最大值。
2、在 NORM_L1、NORM_L2、NORM_INF 模式下,alpha 表示执行相应归一化后矩阵的范数值,beta 不使用。
3、稀疏矩阵归一化仅支持非零像素

透视变换矩阵计算

1
2
3
4
5
6
7
8
9
10
11
12
13

findHomograpy:单应矩阵有 8 个未知数,(3*3,其中第 9 个数为 1),需 4 个点对。

- 会拿>=4 个点去计算,其是不断从一堆点中重复拿出 4 个点去计算出一个结果,再采用一些优化算法 RANSAC/LMEDS 去筛选出最优解

getPerspectiveTransform:

- 采用 SVD 分解的计算方法,只拿前四个点去计算

```python
inv_m=np.linalg.inv(m)
shijiao_r=cv2.Rodrigues(shijiao_r)[0] # 转旋转矩阵
falan_shijiao_r=np.dot(falan_r,shijiao_r)
1
2
3
4
5
6
7
8
9
from cv2 import cv2
import numpy as np
import math
np.set_printoptions(suppress=True)
tr = [-0.00598116, 0.0771877, -0.100618, 0.643282, 357.379, 2.39883]
r = [0.643282, 357.379, 2.39883]
for i in range(len(r)):
r[i] = r[i]*math.pi/180
print(cv2.Rodrigues(np.array(r))[0])

霍夫变换

霍夫变换:Hough Transform:图像处理中的一种特征提取技术,投票算法检测具有特定形状的物体

描述物体的位置与姿态

旋转矩阵:一个向量乘以旋转矩阵等价于向量以某种方式进行旋转。

旋转向量:

  • 旋转向量的长度(模)表示绕轴逆时针旋转的角度(弧度)
  • 便于计算机计算,

欧拉角: 任何一个旋转可以表示为依次绕着三个旋转轴旋三个角度的组合。这三个角度称为欧拉角。

  • 便于用户操作,检查运算结果的正确性

四元数:

罗德里格斯(Rodrigues)变换进行转换

1
2
3
4
R = np.zeros((3, 3), dtype=np.float64)
cv2.Rodrigues(rvecs, R)
rvecs = np.zeros((1, 1, 3), dtype=np.float64)
rvecs,_ = cv2.Rodrigues(R, rvecstmp)

aruco

  • 一个 aruco marker 是一个二进制平方标记,它由一个宽的黑边和一个内部的二进制矩阵组成,内部的矩阵决定了它们的 id。黑色的边界有利于快速检测到图像,二进制编码可以验证 id,并且允许错误检测和矫正技术的应用。marker 的大小决定了内部矩阵的大小。例如,一个 4x4 的 marker 由 16bits 组成。
  • 码占据图像百分比越高 + 图像越清晰 + 越靠近图像中心(畸变小)=效果越好

交互 - 回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import cv2
import numpy as np

# 当鼠标按下时为True
drawing = False
# 如果mode为true时绘制矩形,按下'm'变成绘制曲线
mode = True
ix, iy = -1, -1

# 创建回调函数
def draw_circle(event, x, y, flags, param):
global ix, iy, drawing, mode
# 当按下左键时返回起始位置坐标
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix, iy = x, y
# 当左键按下并移动时绘制图形,event可以查看移动,flag查看是否按下
elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON:
if drawing == True:
if mode == True:
cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1)
else:
# 绘制圆圈,小圆点连在一起就成了线,3代表笔画的粗细
cv2.circle(img, (x, y), 3, (0, 0, 255), -1)

# 当鼠标松开时停止绘图
elif event == cv2.EVENT_LBUTTONUP:
drawing == False

'''
下面把回调函数与OpenCV窗口绑定在一起,在主循环中奖'm'键与模式转换绑定在一起
'''
img = np.zeros((500, 500, 3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_circle)
while 1:
cv2.imshow('image', img)
k = cv2.waitKey(1)
if k == ord('m'):
mode = not mode
elif k == ord('q'):
break
cv2.destroyAllWindows()

遇到的问题

  • cv2 读写 .jpg 图像时,仅读 + 写,得到的 .jpg 图像与原图不一样
  • 因为每次 cv2 写.jpg 图像时都会进行有损压缩,建议保存为 .png 无损压缩即可。

读中文路径

  • 20200502, Linux, opencv-python v4.5.1,已可正常读写中文路径,Nice!
  • but,后来发现 Windows 上还是不行 opencv-python v4.5.2.54,研究后基本确认是由于 windows 是 gbk 编码,改为 utf-8 编码又怕引发莫名的问题,暂时无解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cv2
import numpy as np
img_cv2=cv2.imdecode(np.fromfile('file_path',dtype=np.uint8),cv2.IMREAD_COLOR)


def cv_imread(file_path):
"""numpy 读取文件,再编码为 cv2 的图片对象"""
img_cv2 = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)
return img_cv2


def cv_imwrite(file_path, img_cv2):
# cv2.imencode(ext='.jpg', img=img_cv2)[1].tofile(dst)
suffix = os.path.splitext(file_path)[-1]
cv2.imencode(suffix, img)[1].tofile(file_path)


TypeError: Argument given by name (‘thickness’) and position (4)
1
cv2.rectangle(image, (left,top), (right , bottom), (255, 0, 0), thickness=2)

图像相减

  • 需注意,opencv 图像相加或相减时,像素值超出 [0,255],(比如负数),会产生意想不到的结果

C++

曾在 C++ 的 OpenCV 中遇到这样的问题:
因 Size 与 Size2f 的数据类型同,得到的棋盘格大小不同,使结果产生偏差。

1
2
3
4
5
cv::Size2f squareSize = cv::Size2f(22.5, 22.5); //测量得到的棋盘格的大小
//22.5 22.5

cv::Size squareSize = cv::Size(22.5, 22.5); //测量得到的棋盘格的大小
//22 22

编译

  • opencv-3.4.0.zip
  • opencv-3.4.0-vc14_vc15.exe
  • opencv-4.1.0-vc14_vc15.exe
  • opencv_contrib-4.5.0.zip

scikit-image

scikit-image:基于 scipy 的图像处理包

  • 将图像作为 numpy 数组处理
  • 常用功能:模糊、增强对比度、缩放等
  • 前置包:numpy+matplotlib+scipy+scikit-image
  • 组件:
    • color 颜色空间变换
    • data 提供一些测试图片和样本数据
    • filters 图像增强,边缘检测,排序滤波器,自动阈值等
    • draw 操作于 numpy 数组上的基本图形绘制,包括线条,矩阵,圆和文本等
    • transform 几何变换和其他变换,如旋转,拉伸和 Radon(拉东) 变换等
    • exposure 图像强度调整,例如,直方图均衡化等
    • feature 特征检测和提取, 例如,纹理分析等
    • graph 图论操作,例如,最短路径
    • measure 图像属性测量,例如,相似度和轮廓
    • morphology 形态学操作,如开闭运算,骨架提取等
    • novice 简化的用于教学目的的接口
    • restoration 修复算法,例如去卷积算法,去噪等
    • segmentation 图像分割为多个区域
    • util 通用工具
    • viewer 简单图形用户界面用于可视

基本使用

  • io 读取/保存/显示图片与视频
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from skimage import io
img_path=""
# 读取
img = io.imread('img_path',as_grey=True)
io.imshow(img)
io.show()
# 保存
io.imsave(fname,arr_img)
# 图片信息
type(img)
img.shape 宽,高,通道数
img.size
img.max() 最大像素值
img.min() 最小像素值
img.mean() 像素平均值
1
2
3
4
5
6
7
8
9
10
# data
from skimage import io,data
img=data.log() #商标图像
img2=data.horse() #马的图像
img3=data.clock() #时钟图像
io.imshow(img)
io.show()
# 图像所在地址
from skimage import data_dir
print (data_dir)

pyvips

pyvips:Python绑定到libvips的库,旨在提供高速度和低内存占用的图像处理解决方案

  • Libvips:用于图像处理的高性能、小内存消耗的库,特别适合大量图像数据的批和凸显任务。
1
pip install pyvips

基本使用

1
2
3
4
5
6
7
8
9
10
from pyvips import Image

# 加载图像
image = Image.new_from_file("example.jpg")

# 调整图像大小到宽度为200像素,保持宽高比
resized_image = image.thumbnail_image(200)

# 保存处理后的图像
resized_image.write_to_file("thumbnail.jpg")
  • 流式处理:pyvips 在处理大图像时优势非常明显,因为它不会将整个图像加载到内存中,而是采用流式处理,这意味着即使是几十 GB 的大图像也可以在内存消耗非常少的情况下进行处理。
1
2
3
4
5
6
7
8
# 读取一张大图像
large_image = pyvips.Image.new_from_file('large_image.tif')

# 进行处理,如缩放
resized_large_image = large_image.resize(0.1)

# 保存处理后的大图像
resized_large_image.write_to_file('resized_large_output.tif')

imghdr

imghdr:【标准库】推测图片类型

pyzbar

pyzbar:二维码识别

1
2
3
4
5
6
7
8
9
10
11
import zbar
img_cv2=cv2.imread("aa.png")
img_gray=cv2.cvtColor(img_cv2)
scanner = zbar.ImageScanner()
scanner.parse_config('enable')
img = Image.open('./test.jpg').convert('L')
w, h = img.size
zimg = zbar.Image(w, h, 'Y800', img.tobytes())
scanner.scan(zimg)
for s in zimg:
print(s.type, s.data)

python-qrcode

qrcode:二维码相关操作

1
pip install python-qrcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import qrcode

qr=qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data('Some data')
qr.make(fit=True)

img=qr.make_image()


import qrcode

# 二维码内容
data = "http://www.baidu.com"
# 生成二维码
img = qrcode.make(data=data)
# 直接显示二维码
img.show()
# 保存二维码为文件
img.save("t.jpg")

muggle-ocr

muggle-ocr:无脑级验证码识别。准确率还可以。

tesseract:光学识别验证码,不太准,还需要安装各种语言包,OUT

1
pip install muggle-ocr

文档

  • 预置模型:
    • ModelType.OCR:识别普通印刷文本
    • ModelType.Captcha:识别 4-6 位简单英数验证码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 导入包
import muggle_ocr

#sdk = muggle_ocr.SDK(model_type=muggle_ocr.ModelType.Captcha)
# 1. 印刷文本
sdk = muggle_ocr.SDK(model_type=muggle_ocr.ModelType.OCR) # 初始化
with open(r"印刷文本.png", "rb") as f:
b = f.read()
text = sdk.predict(image_bytes=b)
print(text)

# 2. 验证码
sdk = muggle_ocr.SDK(model_type=muggle_ocr.ModelType.Captcha)
with open(r"验证码.png", "rb") as f:
b = f.read()
text = sdk.predict(image_bytes=b)
print(text)

# 3. 自定义模型,支持基于 https://github.com/kerlomz/captcha_trainer 框架训练的模型
# 训练完成后,进入导出编译模型的[out]路径下, 把[graph]路径下的 pb 模型和[model]下的 yaml 配置文件放到同一路径下。
# 将 conf_path 参数指定为 yaml 配置文件 的绝对或项目相对路径即可,其他步骤一致,如下示例:
with open(r"test3.jpg", "rb") as f:
b = f.read()
sdk = muggle_ocr.SDK(conf_path="./ocr.yaml")
text = sdk.predict(image_bytes=b)

pyexiv2

pyexiv2:操作图片的元数据

  • 元数据的读取速度与元数据的数量成反比,不管图片的大小如何。
1

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Image:
def __init__(self, filename, encoding='utf-8')
def close(self)

def read_exif(self, encoding='utf-8') -> dict
def read_iptc(self, encoding='utf-8') -> dict
def read_xmp(self, encoding='utf-8') -> dict
def read_raw_xmp(self, encoding='utf-8') -> str
def read_comment(self, encoding='utf-8') -> str
def read_icc(self, encoding='utf-8') -> bytes

def modify_exif(self, data: dict, encoding='utf-8')
def modify_iptc(self, data: dict, encoding='utf-8')
def modify_xmp(self, data: dict, encoding='utf-8')
def modify_comment(self, data: str, encoding='utf-8')
def modify_icc(self, data: bytes)

def clear_exif(self)
def clear_iptc(self)
def clear_xmp(self)
def clear_comment(self)
def clear_icc(self)

class ImageData(Image):
def __init__(self, data: bytes)
def get_bytes(self) -> bytes


set_log_level(level=2)
  • 如果你尝试修改一个非标准的标签,则可能引发一个异常
  • 某些特殊的标签不能被 pyexiv2 修改,如 img.modify_xmp({'Xmp.xmpMM.History': 'type="Seq"'})
  • 修改元数据的速度与图片的大小成反比。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 读
with pyexiv2.Image(img_path) as img:
data=img.read_exif()
data=img.read_iptc()
data=img.read_xmp()

# 写,img.modify_exif() 和 img.modify_iptc()同理
dict1 = {'Xmp.xmp.CreateDate': '2019-06-23T19:45:17.834', # 给一个标签赋值。这将覆盖该标签的原始值,如果不存在该标签则添加它
'Xmp.xmp.Rating': ''} # 赋值一个空字符串会删除该标签
img.modify_xmp(dict1)
dict2 = img.read_xmp() # 检查结果
# Image.clear_*()
调用 img.clear_exif() 将删除图片的所有 EXIF 元数据。一旦清除元数据,pyexiv2 可能无法完全恢复它。
按类似的方式使用 img.clear_iptc() 和 img.clear_xmp() .
给博主来一杯卡布奇诺