[深度学习]Part1 Python学习进阶Ch24图像处理OpenCV(24.1~24.13)——【DeepBlue学习笔记】

本文仅供学习使用

Python高级——Ch24图像处理OpenCV(24.1~24.13)


24. 图像处理OpenCV

OpenCV(Open Source Computer Vision Library)是一个基于(开源免费)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android、ios等操作系统上,它轻量级而且高效—由一系列C函数和少量C++类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的诸多通用算法。

参考网址
http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_tutorials.html
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_tutorials.html
http://docs.opencv.org/2.4/genindex.html

OpenCV模块简介:
import cv2
【core】–核心功能模块,包含OpenCV基本数据结构和绘图函数等
【imgproc】–Image/Process,包含滤波、几何变换、直方图相关、 特征检测、运动分析和形状描述等
【highgui】–高层图形用户界面,包含媒体输入输出、视频捕捉、 图像和视频的编码解码、图形交互界面接口等
【calib3d】–Calibration和3D,相机标定和三维重建相关
【contrib】–新增人脸识别、立体匹配、人工视网膜模型等技术
【features2d】–2D功能框架,主要包含特征检测和描述及接口
【flann】–快速最近邻搜索算法和聚类
【gpu】–运用GPU加速的计算机视觉模块
【legacy】–(弃用)包含运动分析、平面细分、描述符提取等
【ml】–Machine Learning,机器学习模块
【objdetect】–目标检测模块(级联分类器和SVM)
【photo】–包含图像修复和图像去噪两部分
【stitching】–图像拼接模块
【……】

OpenCV安装
pip install opencv-python

import cv2
print(cv2.__version__)

一个简单的程序

import cv2
#图像的基本操作
img = cv2.imread('datas/fengjing.jpg')

cv2.imshow('img',img)

k = cv2.waitKey(0)  # 等待按键
print(k)
if k == 27:         # wait for ESC key to exit
    cv2.destroyAllWindows()
elif k == ord('s'): # wait for 's' key to save and exit
    cv2.imwrite('datas/messigray.png',img)
    cv2.destroyAllWindows()  # 销毁所有窗口

24.1 图像读取、显示、保存

OpenCV图像处理系统一般组成:
在这里插入图片描述

24.1.1 图像的基本操作

图像读取:cv2.imread()
窗口创建:cv2.namedWindow()
图像显示:cv2.imshow()
图像保存:cv2.imwrite()
资源释放:cv2.destroyWindow()
在这里插入图片描述

import cv2
#图像的基本操作

img = cv2.imread('datas/fengjing.jpg')  # 读取图像
cv2.imshow('img',img)  # 显示原图

hsv_img = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)  # 转为HSV图像
cv2.namedWindow("hsv",cv2.WINDOW_AUTOSIZE)  # 创建hsv窗口

cv2.imshow("hsv",hsv_img)  # 显示hsv图像
cv2.imwrite("hsv.jpg",hsv_img)  # 保存hsv图像
cv2.waitKey(0)  # 等待按键按下
cv2.destroyAllWindows()  # 销毁所有窗口

cv2.imread(filename, [flags])
第一个参数,需要填入图片路径名,支持如下格式:
· Windows bitmaps -*.bmp,*.dib(always supported)
· JPEG files-*.jpeg,*.jng,*.jpe (see the Motes section)
· JPEG 2000 files -*.jp2(see the Ntes section)
· Portable Network Graphics -*png (see the Motes section)
· Portable image format-*.pbm,*.pgm,*.ppm (always supported)
· Sun rasters -*.sr,*.ras(always supported)
· TIFF files -*.tiff,*.tif(see the Notes section)
注意路径格式用\或/,同时路径中不要有中文
第二个参数,指定加载图像的颜色类型 ,默认为1:
cv2.IMREAD cOLOR # 读入彩色图像,透明度被忽略,对应宏定义为1(默认参数)
cv2.IMREAD GRAYSCALE # 以灰度模式读入图像,对应宏定义为0
cv2.IMREAD UNCHANGED # 读入图像包含alpha通道,对应宏定义为-1
警告∶就算图像的路径是错的,OpenCV 也不会提醒你的,但是当你使用命令print img时得到的结果是None。

通过判断img是否为None来避免异常发生

import cv2

img0 = cv2.imread('datas/fengjing.jpg', cv2.IMREAD_GRAYSCALE)#0
if img0 is None:
    print('imgread is error')
else:
    print('imgread is ok')
    cv2.imshow('img0',img0)
    print(img0.shape)  # (546,820)  一个通道
    
img1 = cv2.imread('datas/fengjing.jpg', 1)#0
if img0 is None:
    print('imgread is error')
else:
    print('imgread is ok')
    cv2.imshow('img1',img1)
    print(img1.shape)  # (546,820,3)  三个通道

#  不同通道下的灰度图——RGB
img1_0 = img1[..., 0]
img1_1 = img1[..., 1]
img1_2 = img1[..., 2]
cv2.imshow('img1_0',img1_0)
cv2.imshow('img1_1',img1_1)
cv2.imshow('img1_2',img1_2)
cv2.waitKey(0)

namedWindow(winnname, [flags])
第一个参数,表示窗口名称,传入字符串即可
第二个参数,窗口显示方式,取值如下:
cv2.WINDOW NORMAL # 正常大小显示,用户可以改变窗口大小
cv2.WINDow AUTOSIZE # 根据图像大小自动调整,用户不能手动改变窗口大小

imshow(winnname, mat)
第一个参数,设置需要显示的窗口名称
第二个参数,填写需要显示的图像

imwrite(filename, img, [params])
第一个参数,设置保存的文件名,需填写后缀,如"1.bmp"
第二个参数,要保存的Mat类型图像数据
第三个参数,表示特定格式保存的参数编码,一般采用默认值不填写
对于JPEG,其表示的是图像的质量,用0~100的整数表示,默认为95
对于PNG,第三个参数表示的是压缩级别,从0到9,压缩级别越高,图像尺寸越小,默认级别为3

waitKey([delay])
第一个参数,如果delay>0, 表示等待delay毫秒之后结束; 如果delay=0, 表示无限等待,直到有按键按下结束
返回值为对应按下按键的ASCII码值, 如Esc的ASCII码为27

k = cv2.waitKey(0)
print(k)
if k == 27:         # wait for ESC key to exit
    cv2.destroyAllWindows()
elif k == ord('s'): # wait for 's' key to save and exit
    cv2.imwrite('datas/messigray.png',img)
    cv2.destroyAllWindows()

destroyWindow(winname) / destroyAllWindow()
cv2.destroyWindow()销毁指定窗口,参数填窗口名称
cv2.destroyAllWindows()销毁所有打开的GUI窗口

img is None 判断图像是否为空(读取是否成功)
img.shape[0] 获取图像行数(高度)
img.shape[1] 获取图像列数(宽度)
img.shape[2] 获取图像通道数
img.size 获取总的像素个数(宽度x高度x通道数)
img.dtype 获取图像的数据类型

import cv2
import matplotlib.pyplot as plt
import numpy as np

img = cv2.imread('datas/fengjing.jpg')
#cv2显示 BGR
cv2.imshow('cv2', img)
cv2.waitKey(0)
#matlab显示 RGB
plt.imshow(img)
plt.show()
#顺序调整
plt.imshow(img[:,:,::-1])
plt.show()

# Release everything if job is finished
cv2.destroyAllWindows()

24.1.2 摄像头/视频读取、写入

VideoCapture类: OpenCV2.x及以上版本新增VideoCapture类,提供了从摄像机或视频文件捕获视频的C++接口。
VideoCapture类三种构造函数方法:

Python: cv2.VideoCapture() → <VideoCapture object>
Python:cv2.VideoCapture(filename) → <VideoCapture object>
Python:ev2.VideoCapture(device) → <VideoCapture object>
参数说明:
参数filename表示输入视频文件的路径及名称
device表示打开摄像头索引号

取帧方法:
Python: cv2.VideoCapture.read([image]) → retval, image

import cv2

# 视频的基本操作 —— 单帧图片 
cap = cv2.VideoCapture(0)  # device
ret, frame = cap.read()  # ret是否能取到帧,frame帧图片(ndarray)
cv2.imshow('frame', frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
import cv2

# 视频的基本操作  —— 视频  
cap = cv2.VideoCapture(0)  # device
ret, frame = cap.read()  # ret是否能取到帧,frame帧图片(ndarray)
count = 0
while True:
    count += 1
    ret, frame = cap.read()
    if not ret:
        break
    if count > 240:
        cap.release()
    # frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)#颜色空间转换
    cv2.imshow('frame', frame)
    cv2.waitKey(1)

VideoCapture类常用函数功能:

cv2.VideoCapture.open(filename) → retval
cv2.VideoCapture.open(device) → retval
如果不能指定摄像头——(0, CAP_DSHOW) DirectShow,windows系统的视频管理系统
open()—打开视频文件或者摄像头

#cap = cv2.VideoCapture(0)  #device
cap = cv2.VideoCapture('datas/vedioplay.mp4') #filename
# cap = cv2.VideoCapture()
# cap.open('datas/vedioplay.mp4')

cv2.VideoCapture.isOpened()→ retval
isOpened()–判断读取视频文件是否正确,正确返回true

print(cap.isOpened())

cv2.VideoCapture.release() → None
release()—关闭视频流文件

cv2.VideoCapture.grab()→ retval
grab()—抓取下一帧的视频文件或设备

cv2.VideoCapture.retrieve([image[,channel]]) → retval, image → retval, image
retrieve()—解码并返回视频帧

cv2.VideoCapture.get(propId) → retval
get()—返回指定视频类的相关参数信息

cv2.VideoCapture.set(propId,value) → retval
set()—设置类信息的一个属性

# 视频属性
import cv2
cap = cv2.VideoCapture('datas/vedioplay.mp4')

# 帧率
fps = cap.get(cv2.CAP_PROP_FPS)  # 25.0
print("Frames per second using video.get(cv2.CAP_PROP_FPS) : {0}".format(fps))

# 总共有多少帧
num_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)
print('共有', num_frames, '帧')
#
frame_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
frame_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
print('高:', frame_height, '宽:', frame_width)

#FRAME_NOW = cap.get(cv2.CAP_PROP_POS_FRAMES)  # 第0帧
#print('当前帧数', FRAME_NOW)  # 当前帧数 0.0

# Release everything if job is finished
cap.release()

读取视频:
说明:视频读取本质上就是读取图像,因为视频是由一帧一帧图像组成的。

  1. 读取视频的两种方法:
# 1
cap = cv2.VideoCapture('datas/vedioplay.mp4') #filename

# 2
cap = cv2.VideoCapture()
cap.open('datas/vedioplay.mp4')
  1. 循环显示每一帧:
while True:
    ret, frame = cap.read()
    if not ret:  # ret == False
        break
    hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 颜色空间转换
    cv2.imshow('frame',  hsv_frame)
    if cv2.waitKey(10) & 0xFF == 27:
        cv2.imwrite('1.bmp', hsv_frame)
        print('1')

几点说明:

  1. 打开的视频可以是本地视频如:cv2.VideoCapture cap("1.avi");
    也可以是网络视频如:cv2.VideoCapture cap("http://www.laganiere.name/bike.avi");

  2. 读取视频一定要加异常判断
    ① 打开视频失败(各种原因)
    ② 取帧失败异常(程序结尾判断)
    ex1:if(cap.isopened())∶#检查打开是否成功
    ex2:

ret,frame = cap.read()
# if(frame is None)∶# 判断当前帧是否为空
if ret==False# 判断取帧是否失败
	break

写入视频:

  1. VideoWriter:
    Opencv提供VideoWriter类写视频文件,类的构造函数可以指定文件名、播放帧率、帧尺寸、是否创建彩色视频。

cv2.VideoWriter([filename, fourcc, fps, frameSize[, isColor]) → <VideoWriter object>
· filename - Name of the output video file.
· fourcc - 4-character code of codec used to compress the frames. For example,CV_FOURCC(‘P’,‘I’,‘M’,‘1’)is a MPEG-1 codec, CV_FOURCC('M,‘J’,‘P’,‘G’) of codes can be obtained at Video Codecs by FOURCC page.
· fps - Framerate of the created video stream.
· frameSize - Size of the video frames.
· isColor - If it is not zero,the encoder will expect and encode color frames,otherwise it will work with grayscale frames(the flag is current)

  1. 写入帧方法

fourcc = cv2.VideoWriter_fourcc(*'XVID')
out= cv2.VideoWriter('output.avi',fourcc,20.0,(640,480))
out.write(frame)

  1. 注意事项:

写入视频前需安装对应的编解码器
生成视频是否支持彩色应与构造函数设置一致
生成视频尺寸需与读取视频尺寸一致

视频保存

import cv2
cap = cv2.VideoCapture('datas/vedioplay.mp4')
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)

#写入帧方法
fourcc = cv2.VideoWriter_fourcc(*'MJPG')  # opencv 3.0 #cv2.VideoWriter_fourcc('X','V','I','D')

#创建一个视频写入对象 VideoWriter
out = cv2.VideoWriter('datas/output20220617-1.mp4', fourcc, 20.0, (int(width), int(height)))
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    out.write(frame)

# Release everything if job is finished
cap.release()
cv2.destroyAllWindows()

24.1.3 简单实例应用

视频分解: 读取视频并分解为一张一张的图片保存

批量读取图片:

  1. 有序图片读取:根据图片名的规律来对文件名进行格式化,然后统一读入;
  2. 无序图片读取:文件名无规律,通过Windows相关方法读取。

**图片合成视频:**将一张一张的图片合成为视频保存

import cv2
import os
import random

# 1.读取datas/2-PI231imgs文件夹中的任意一张图片,并查看该图片的通道数。
path_org = r"F:\2-PI231imgs"  # 文件夹路径
File_in_path = os.listdir(path_org)  # 获取该文件夹下所有的文件名 的一个列表
random.seed(10)
Choice_File = random.choice(File_in_path)  # 随机抽取一个文件
path_get = os.path.join(path_org,Choice_File)  # 随机文件的地址

img_get = cv2.imread(path_get)  # 读取文件图片(默认为彩色图)

if img_get is None:
    print(f'文件{Choice_File}读取地址有误')
else:
    print(f'文件{Choice_File}的通道数为{img_get.shape[-1]}')


# 2.将 datas/2-PI231imgs文件夹中的图片保存成格式为mp4的视频
Files_in_path = [os.path.join(path_org,File_path) for File_path in File_in_path]
print(Files_in_path)
# 获取文件的尺寸大小
test_img = cv2.imread(Files_in_path[0])
if test_img is None:
    print("图片读取错误!")
else:
    height = int(test_img.shape[0])
    wid = int(test_img.shape[1])
    print(height,wid)
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    out= cv2.VideoWriter('output.avi',fourcc,20,(wid,height))
    for path in Files_in_path:
        img_in_path = cv2.imread(path)
        # cv2.waitKey(3)
        # cv2.imshow('test',img_in_path)
        out.write(img_in_path)
    

out.release()
cv2.destroyAllWindows()
print('视频以保存')

24.2 常用数据结构与颜色空间

24.2.1 OpenCV常用数据结构

  1. Point类
    Point类数据结构表示二维坐标系的点,由坐标x, y指定的2D点,Python中直接用元组(x,y)
  2. Rect类
    Rect类用来表示矩形,成员有x,y, width, height,Python中直接用元组(x,y,w,h)
    在这里插入图片描述
  3. Size类
    Size表示区域大小,常用构造函数Size(int _width, int _height),Python中直接用元组(width, height)
  4. Scalar类
    Scalar()表示具有四个元素的数组,大量用来传递像素值,如RGB颜色,Python中直接用元组(B, G, R)
    一般形式:Scalar(double B, double G, double R, double Alpha)
    如果用不到第四个则表示Scalar(B, G, R), 其中:B—表示蓝色分量G—表示绿色分量R—表示红色分量Alpha—表示透明度

注意:Scalar表示颜色顺序为BGR
Scalar(255, 0, 0) ----表示纯蓝色
Scalar(0, 255, 0) ----表示纯绿色
Scalar(0, 0, 255) ----表示纯红色
Scalar(255, 255, 0) ----表示青色
Scalar(0, 255, 255) ----表示黄色

24.2.2 图像基础

  1. 基础图像容器:
    Python用numpy来操作
  2. Numpy相关属性简介:
    np is None 数组是否为空
    np.shape 数组行,列,维度
    img.size 数组元素总个数
    img.dtype 数组元素数据类型
  3. Numpy相关方法简介:
    np.zeros()创建全为元素0的数组
    np.ones()创建全为元素1的数组
    np.eye()创建对角线元素全为1的数组
    np.copy()数组深拷贝
import numpy as np 
import cv2

# img=np.zeros((512,512,3),np.uint8)
# img=np.ones((512,512,3),np.uint8)
img=np.eye(100,100)
img = 255 * img
# print(img)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
  1. 常用颜色空间介绍:
    RBG颜色空间——R/G/B取值范围[0,255]
    在这里插入图片描述

HSV/HLS颜色空间——H取值范围[0,360],S/V取值范围[0,1]
H:色调,0度(红色)
S:饱和度,接近某种光谱色的程度,值越大表示越接近。
V:亮度,值越大亮度越高。
主要用于:颜色识别(跟踪)
在这里插入图片描述

Lab颜色空间——L取值范围[0,100]a/b取值范围[127,-128]
在这里插入图片描述

import numpy as np
import cv2

size = (2560, 1600)
# 全黑.可以用在屏保
black = np.zeros(size)
black[225:300] = 255
# black[:,225:300] = 255
black[34,56] = 255
print(black[34][56])
cv2.imshow('black', black)
cv2.waitKey(0)
cv2.imwrite('black.jpg',black)

#white 全白
black[:]=255
print(black[34][56])
cv2.imshow('white', black)
cv2.waitKey(0)
cv2.imwrite('white.jpg',black)

24.2.3 基本绘图函数

OpenCV常用绘图函数:
cv2.line()函数------用于绘制直线
cv2.circle()函数------用于绘制圆
cv2.rectangle()函数------用于绘制矩形
cv2.ellipse()函数------用于绘制椭圆
cv2.fillPoly()/polylines()函数-----用于绘制多边形
cv2.putText()函数------用于添加文字

绘制直线——cv2.line()
cv2.line(img,pt1, pt2, color[, thclaness[, lineTye[, shift]]]) → None

绘制圆——cv2.circle()
cv2.circle(img, center,radius,color[,thickness[, lineType[,shift]]]) → None
Opencv2—cv2.CV_AA, OpenCV3—cv2.LINE_AA

绘制矩形——cv2.rectangle()
cv2.rectangle(img,pt1,pt2,color[,thickness[,lineType[, shift]]]) → None

绘制椭圆——cv2.ellipse()
cv2.ellipse(img,center, axes,angle, startAngle,endangle,color[,thickness[,lineTye[,shift]]]) → None
cv2.ellipse(img, box,color[,thicknese[,linetype]]) → None

绘制多边形——cv2.fillPoly()/cv2.polylines()
cv2.fillPoly(img,pts,color[,lineType,shift[,offset]]]) → None
cv2.polylines(img,pts,isClosed,color[, thickmess[,lineType[,shift]]])→ None

添加文字——cv2.putText()
cv2.puText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomleftOrigin]]])→ None

# -*- coding: utf-8 -*-
import numpy as np
import cv2

'''
• img: 你想绘制图形的 幅图像。
• color: 形状的颜色。以RGB为例,需要传入一个元组 BGR 例如 255,0,0 
   代表蓝色,第一个是蓝色通道,第二个是绿色通道,第三个是红色通道。对于灰度图只需要传入灰度值。
• thickness 线条的粗细。如果给一个闭合图形置为 -1  那么这个图形就会被填充。默认值是 1.
• linetype 线条的类型, 8 连接,抗锯齿等。  默认情况是8 连接。cv2.LINE_AA
   为抗锯齿,这样看起来会非常平滑。

'''
#0 1 2 3.....255

# Create a black image
img = np.ones((512, 512, 3), np.uint8)
cv2.imshow(winname='img', mat=img)
cv2.waitKey(1)
# Draw a diagonal blue line with thickness of 5 px
cv2.line(img, pt1=(0, 0), pt2=(511, 511), color=(255, 0, 0), thickness=5)  # pt1, pt2, color, thickness=5

# cv2.polylines() 可以用来画很多条线。只需要把想画的线放在一 个列表中, 
#                 将列表传给函数就可以了。每条线会被独立绘制。这会比用 cv2.line() 一条一条的绘制要快一些。
# cv2.polylines(img, pts, isClosed, color, thickness=None, lineType=None, shift=None)

cv2.arrowedLine(img,pt1=(151, 401), pt2=(21, 13), color=(255, 0, 0), thickness=5)

cv2.rectangle(img, (384, 0), (510, 128), (0, 255, 0), -1)  # (x,y,h,w)  (x,y)   (h,w)

cv2.circle(img, center=(447, 63), radius=63, color=(0, 0, 255), thickness=-1)  # center, radius, color, thickness=None

# 一个参数是中心点的位置坐标。 下一个参数是长轴和短轴的长度。椭圆沿逆时针方向旋转的角度。
# 椭圆弧演顺时针方向起始的角度和结束角度 如果是 0 和 360 就是整个椭圆
cv2.ellipse(img, center=(256, 256), axes=(100, 50), angle=30, startAngle=0, endAngle=180, color=255,
            thickness=-1)  # center, axes, angle, startAngle, endAngle, color, thickness=

pts = np.array([[10, 5], [20, 30], [70, 20], [50, 10]], np.int32)
pts = pts.reshape((-1, 1, 2))
# 这里 reshape 的第一个参数为-1, 表明这一维的长度是根据后面的维度的计算出来的。

cv2.polylines(img, [pts], True, (0, 255, 255))
# 注意 如果第三个参数是 isClosed=False 我们得到的多边形是不闭合的,首尾不相连。

font = cv2.FONT_HERSHEY_SIMPLEX # 添加文本的字体类型

# org :Bottom-left corner of the text string in the image.左下角
# 或使用 bottomLeftOrigin=True,文字会上下颠倒
cv2.putText(img, text='bottomLeftOrigin', org=(10, 400), fontFace=font, fontScale=1, color=(255, 255, 255), thickness=1,bottomLeftOrigin=True)
# text, org, fontFace, fontScale, color, thickness=
cv2.putText(img, text='OpenCV', org=(10, 500), fontFace=font, fontScale=4, color=(255, 255, 255), thickness=2)
# text, org, fontFace, fontScale, color, thickness=
cv2.imshow(winname='img', mat=img)
cv2.waitKey(0)
# 所有的绘图函数的返回值都是 None ,所以不能使用 img = cv2.line(img,(0,0),(5

# winname = 'example'
# cv2.namedWindow(winname, 0)
# cv2.imshow(winname, img)

cv2.imwrite("example.png", img)

cv2.waitKey(0)
cv2.destroyAllWindows()

24.2.4 访问图像像素

预备知识:
图像矩阵的大小取决于所用的颜色模型(或者说通道数),灰度图矩阵如下:
在这里插入图片描述
多通道图像,如RGB颜色模型的矩阵如下:
在这里插入图片描述
注:opencv的通道顺序是BGR,而不是RGB

访问图像中像素方法:

import cv2
import numpy as np

img = (np.ones((500,500,3),np.uint8))*255

print(img[100,200])
img[100,200] = 0
print(img[100,200])
img[100:200,:] = 0
cv2.imshow('img',img)
cv2.waitKey(0)

img[100:200,:] = [0,255,255]
cv2.imshow('img',img)
cv2.waitKey(0)

# 访问每个像素点
img = (np.ones((500,500,3),np.uint8))*255
for x in range(img.shape[1]):
    for y in range(img.shape[0]):
        if x == y and:
            img[y,x] == [255,0,255]
cv2.imshow('img',img)
cv2.waitKey(0)     

简单应用——雪花效果

简单应用——减色效果

import cv2
import numpy as np

img = cv2.imread(r'F:\2-PI231imgs\001.jpg')
cv2.imshow('img1',img)
cv2.waitKey(0)      

cv2.imshow('img2',img-30)
cv2.waitKey(0)      


# 访问每个像素点
for x in range(img.shape[1]):
    for y in range(img.shape[0]):
        if x == y:
            img[y,x] == [255,0,255]

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

24.2.5 对比度亮度调整与通道分离合并

对比度亮度调整:

  1. 原理介绍
    g ( x ) = a ⋅ f ( x ) + b g\left( x \right)=a\cdot f\left( x \right)+b g(x)=af(x)+b g ( i , j ) = a ⋅ f ( i , j ) + b g\left( i,j \right)=a\cdot f\left( i,j \right)+b g(i,j)=af(i,j)+b
    参数f(x)表示原图像像素
    参数g(x)表示输出图像像素
    参数a(a>0),被称为增益(gain), 通常用来控制图像的对比度
    参数b通常被称为偏置(bias), 通常用来控制图像的亮度

  2. 方法

img = img * 2 -100

cv2.imshow('img3',img)
cv2.waitKey(0) 

通道分离与合并:

  1. 通道分离:cv2.split()函数

  2. 通道合并:cv2.merge()函数

import cv2
import numpy as np

img = cv2.imread(r'F:\2-PI231imgs\001.jpg')  # 读取图像
cv2.imshow('src',img)
print(img.shape)  # 打印图像属性
b,g,r=cv2.split(img)
cv2.imshow('B',b)  # 蓝色通道
cv2.waitKey(0)   
cv2.imshow('G',g)  # 绿色通道
cv2.waitKey(0)   
cv2.imshow('R',r)  # 红色通道
cv2.waitKey(0)   
dst=cv2.merge([b,g,r])  # 通道合并
cv2.imshow('merge',dst)  # 通道合并
cv2.waitKey(0)   

img=cv2.imread(r'F:\2-PI231imgs\001.jpg')  # 读取图像
cv2.imshow('src',img)
print(img.shape)  # 打印图像属性
b = img[:,:,0]  # 蓝色通道
g = img[:,:,1]  # 绿色通道
r = img[:,:,2]  # 红色通道
cv2.imshow('B',b)  # 蓝色通道
cv2.waitKey(0)   
cv2.imshow('G',g)  # 绿色通道
cv2.waitKey(0)   
cv2.imshow('R',r)  # 红色通道
cv2.waitKey(0)   
dst=cv2.merge([b,g,r])  # 通道合并
cv2.imshow('merge',dst)  # 通道合并
cv2.waitKey(0)   

numpy:

np.stack((b,g,r),2).shape

24.2.6 图像翻转——Numpy

import cv2
import numpy as np

img = cv2.imread(r'F:\2-PI231imgs\001.jpg')  # 读取图像

img_BGR2RGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
cv2.imshow('src',img)
cv2.waitKey(0) 

img_np = img_BGR2RGB[::-1]  # 上下
cv2.imshow('src2',img_np)
cv2.waitKey(0) 

img_np = img_BGR2RGB[:,::-1]  # 左右
cv2.imshow('src3',img_np)
cv2.waitKey(0) 

img_np = img_BGR2RGB[::-1,::-1]  # 中心对称
cv2.imshow('src4',img_np)
cv2.waitKey(0) 

img_np = img_BGR2RGB[...,::-1]  # 通道翻转——RGB-BGR  img_BGR2RGB[...,::[2,1,0]]
cv2.imshow('src5',img_np)
cv2.waitKey(0) 

24.3 图像基本运算

图像的基本运算,是两张图片相应位置的元素进行运算——满足广播条件(形状相同)

24.3.1 图像算数运算

  1. 图像加法——优先使用OpenCV接口

cv2.add(src1,src2[,dst[,mask[,dtype]]]) → dst 图像加法
cv2.addWeighted(src1,alpha,src2,beta,gamma[,dst[,dtype]]) → dst 图像权重叠加
dst(I)=saturate(src1(I) * alpha+src2(I) * beta + gamma)

import cv2
import numpy as np

img1 = cv2.imread(r'F:/datas/fengjing1.png')  # (546, 820, 3)
img2 = cv2.imread(r'F:/datas/logo.png')  # (300, 244, 3)
print(img1.shape)  
print(img2.shape)
# img1resize = cv2.resize(img1, (512, 512) )
# print(img1resize.shape)  # (512, 512, 3)
img1resize = cv2.resize(img1, img2.shape[1::-1] )
print(img2.shape[1::-1])  # (244, 300)
print(img1resize.shape)  # (300, 244, 3)
img1resize2 = cv2.resize(img1, img2.shape[0:2] )
print(img1resize2.shape)  # (244, 300, 3)

# cv2.imshow('img2-1',img1resize)
# cv2.waitKey(0)

# cv2.imshow('img2-2',img1resize2)
# cv2.waitKey(0)

# 加法
dst = cv2.add(img1resize,img2)
dstw = cv2.addWeighted(img1resize,0.6,img2,-1,300)
dst2 = img1resize + img2
cv2.imshow('add',np.hstack([dst,dstw]))
cv2.waitKey(0)
cv2.imshow('add',dst2)
cv2.waitKey(0)
  1. 图像减法

cv2.subtract(src1,src2[,dst[,mask[,dtype]]]) → dst
cv2.absdiff(src1,src2[,dst]) → dst

import cv2
import numpy as np

img1 = cv2.imread(r'F:/datas/fengjing1.png')  # (546, 820, 3)
img2 = cv2.imread(r'F:/datas/logo.png')  # (300, 244, 3)
img1resize = cv2.resize(img1, img2.shape[1::-1] )
img1resize2 = cv2.resize(img1, img2.shape[0:2] )

#减法
dst2 = cv2.subtract(img1resize, img2)
dstw = cv2.absdiff(img1resize, img2)
cv2.imshow('sub', np.hstack([dstw, dst2]))
cv2.waitKey(0)

图像加减法都需要两个图像有相同的大小和类型

  1. 图像乘除法
dst2 = 2 * img1 -200
dstw = img1 / 3 -20
cv2.imshow('sub', np.hstack([dstw, dst2]))
cv2.waitKey(0)

24.3.2 图像逻辑运算

  1. 图像相与 &(&=)

c2.bitwise_and(src1, src2,dst[, mask]]) → dst

  1. 图像相或 | (|=)

c2.bitwise_or(src1, src2,dst[, mask]]) → dst

  1. 图像异或 (=)

c2.bitwise_xor(src1, src2,dst[, mask]]) → dst

import numpy as np
import cv2
from matplotlib import pyplot as plt

# 1.与运算
fig, ((axes1, axes2, axes3), (axes4, axes5, axes6)) = plt.subplots(2, 3)
xiaoxin = cv2.imread('datas/xiaoxin.png')
h, w, c = xiaoxin.shape
wcircle = np.zeros_like(xiaoxin, dtype=np.uint8)  # 创建一张全黑的图片
cv2.circle(wcircle, (w//2, h//2), (h+4)//2, color=(255, 255, 255), thickness=-1)
# dst = xiaoxin & wcircle
dst = cv2.bitwise_and(xiaoxin, wcircle)  # 与
axes1.imshow(xiaoxin[..., ::-1])
axes2.imshow(wcircle)
axes3.imshow(dst[..., ::-1])
axes1.set_title('xiaoxin')
axes2.set_title('wcircle')
axes3.set_title('xiaoxin and wcircle')

# cv2.imwrite('datas/xiaoxin_1.png', dst)

# 2.或运算
xiaoxin_1 = cv2.imread('datas/xiaoxin_1.png')
# bcircle = np.where(wcircle==0, 255, 0)
bcircle = cv2.bitwise_not(wcircle)  # 取反
# dst = xiaoxin | bcircle  # 或
dst = cv2.bitwise_or(xiaoxin_1, bcircle)  # 或

axes4.imshow(xiaoxin_1[..., ::-1])
axes5.imshow(bcircle)
axes6.imshow(dst[..., ::-1])
axes4.set_title('xiaoxin_1')
axes5.set_title('bcircle')
axes6.set_title('xiaoxin_1 or bcircle')
plt.show()

# cv2.imwrite('datas/xiaoxin_2.png', dst)

# 3.异或运算
fig, (axes1, axes2, axes3) = plt.subplots(1, 3)
xiaoxin = cv2.imread('datas/xiaoxin.png')
h, w, c = xiaoxin.shape
white = np.ones_like(xiaoxin, dtype=np.uint8) * 255  # 创建一张全白的图片
cv2.circle(white, (w//2, h//2-50), 150, color=(0, 0, 0), thickness=-1)
dst = xiaoxin ^ white
axes1.imshow(xiaoxin[..., ::-1])
axes2.imshow(white)
axes3.imshow(dst[..., ::-1])
axes1.set_title('xiaoxin')
axes2.set_title('white')
axes3.set_title('xiaoxin nor white')
plt.show()

cv2.imwrite('datas/xiaoxin_3.png', dst)

24.3.2 图像复制

浅拷贝:temp = img
深拷贝:temp = img.copy()

# encoding:utf8
import cv2
import numpy as np
import random
# 读取一张图片,并显示出其雪花效果

car = cv2.imread(r'F:/datas/car.jpg')  # (800, 1280, 3)

def snowlean_show(photo,num = 1000):
    if photo is not None:
        org = photo.copy()
        for i in range(num):
            xi = random.choice(range(photo.shape[1]))
            yi = random.choice(range(photo.shape[0]))
            ri = random.choice(range(int(min(photo.shape[0],photo.shape[1])/100)))

            # 绘制圆形白点
            cv2.circle(photo, center=(xi,yi),radius=ri,color=[255,255,255],thickness=-1)
        cv2.imshow('snowlean_show',np.hstack([photo,org]))
        cv2.waitKey(0)
    else:
        print('图片导入有误!')

snowlean_show(car)

24.4 图像ROI与mask掩码

24.4.1 感兴趣区域ROI

  1. ROI介绍
    ROI—(region of interest)—感兴趣区域:一般为矩形区域;能够确定分析重点,减少处理时间,提高精度
    定义方法:使用Rect起点终点范围

  2. ROI选取
    注意ROI参数顺序y1: y2, x1:x2

import cv2
import numpy as np

car = cv2.imread(r'F:/datas/car.jpg')  # (800, 1280, 3)
cv2.imshow('src',car)
cv2.waitKey(0)
car_roi = car[250:1000, 200:600]  # y1:y2 , x1:x2
cv2.imshow('src2',car_roi)
cv2.waitKey(0)

24.4.2 mask—掩码/掩膜操作

mask—(掩码)—是一个8位单通道图像(灰度图/二值图) ,掩码某个位置如果为0,则在此位置上的操作不起作用;掩码某个位置如果不为0,则在此位置上的操作会起作用,可以用来提取不规则ROI
在这里插入图片描述

(见24.8)

阈值函数cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
src:表示的是图片源
thresh:表示的是阈值(起始值)
maxval:表示的是最大值
type:表示算法类型, 常用值为0(cv2.THRESH_BINARY)
retval:表示返回的阈值。若是全局固定阈值算法,则返回 hresh 参数值。若是全局自适应阈值算法,则返回自适应计算得出的合适阈值。
dst:表示输出与src相同大小和类型以及相同通道数的图像。

二值化阈值处理
THRESH_BINARY: d s t ( x , y ) = { m a x V a l i f   s r c ( x , y ) > t h r e s h 0 o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} maxVal \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\0\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={maxValifsrc(x,y)>thresh0otherwise
反二值化阈值处理
THRESH_BINARY_INV: d s t ( x , y ) = { 0 i f   s r c ( x , y ) > t h r e s h m a x V a l o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} 0 \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\maxVal\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={0ifsrc(x,y)>threshmaxValotherwise
截断阈值化处理
THRESH_TRUNC: d s t ( x , y ) = { t h r e s h o l d i f   s r c ( x , y ) > t h r e s h s r c ( x , y ) o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} threshold \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\src(x,y)\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={thresholdifsrc(x,y)>threshsrc(x,y)otherwise
超阈值零处理
THRESH_TOZORE: d s t ( x , y ) = { s r c ( x , y ) i f   s r c ( x , y ) > t h r e s h 0 o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} src(x,y) \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\0\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={src(x,y)ifsrc(x,y)>thresh0otherwise
低阈值零处理
THRESH_TOZORE_INV: d s t ( x , y ) = { 0 i f   s r c ( x , y ) > t h r e s h s r c ( x , y ) o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} 0 \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\src(x,y)\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={0ifsrc(x,y)>threshsrc(x,y)otherwise

import cv2
import numpy as np

car = cv2.imread(r'F:/datas/car.jpg')  # (800, 1280, 3)
logo = cv2.imread(r'F:/datas/logo.png') # (300, 244, 3)

def getmask(logo, thred=200):
    # logo:bgr模式
    # 转灰度图
    logo_gray = cv2.cvtColor(logo, cv2.COLOR_BGR2GRAY)
    # 转二值图(阈值的操作)
    logo_thred = np.where(logo_gray < thred, 0, 255).astype(np.uint8)   
    return logo_thred

# 1.获取背景
# 1.1 获取ROI,ROI的shape:
car_roi = car[0:logo.shape[0], 0:logo.shape[1]]
# 1.2 获取掩码
mask = getmask(logo)
retval, dst = cv2.threshold(logo, 50, 255, cv2.THRESH_BINARY)

cv2.imshow('car_roi', np.hstack([car_roi,dst]))
cv2.waitKey(0)
cv2.imshow('mask', mask)
cv2.waitKey(0)
print('retval',retval)  # 50

24.4.3 ROI图像融合

import cv2
import numpy as np

# 作业1. 高级融合
def get_mask(InMat: np.ndarray, ThreNum=200):
    """
    返回阈值大于ThreNum的区域
    """
    # 先把3通道的图像转化为单通道
    if InMat.ndim == 3:
        InMat = cv2.cvtColor(InMat, cv2.COLOR_BGR2GRAY)

    # 过滤选择出大于ThreNum的区域
    OutMat = np.where(InMat >= ThreNum, 255, 0).astype(np.uint8)  
    # 对每个元素进行判断; 需制定元素的格式为np.uint8
    return OutMat

def work1():
    # 找到掩膜
    LogoPath = r"F:/datas/logo.png"
    LogoImg = cv2.imread(LogoPath)
    h, w, _ = LogoImg.shape
    mask = get_mask(LogoImg, 170)   # 掩膜1
    mask_inv = 255 - mask           # 掩膜2
    # mask_inv = cv2.bitwise_not(mask)

    # 抠出logo彩色区域
    MaskLogoImg = cv2.bitwise_and(LogoImg, LogoImg, mask=mask_inv)      # "mask="这个标识符不可少

    # 读入汽车图像
    CarPath = r"F:\datas\car.jpg"
    CarImg = cv2.imread(CarPath)
    start_x, start_y = 50, 50       # 定义贴图的左上角起始点
    CarRoi = CarImg[start_y:start_y + h, start_x:start_x + w]
    # MaskCarRoi = cv2.bitwise_and(CarRoi, CarRoi, mask=mask)
    MaskCarRoi = cv2.copyTo(CarRoi, mask=mask)          # cv2.copyTo()也响应掩膜, 只复制掩膜对应的区域
    cv2.imshow('mask', np.hstack([MaskLogoImg, MaskCarRoi]))
    cv2.waitKey(0)    
    ShowCarRoi = cv2.add(MaskLogoImg, MaskCarRoi)       # 两张图像相加
    CarImg[start_y:start_y + h, start_x:start_x + w] = ShowCarRoi

    # 显示图像
    cv2.imshow('CarImg',CarImg)
    cv2.waitKey(0)
    
# work1()
import cv2
import numpy as np

VideoPath="F:/datas/objecttracking2.avi"
# 读入视频
cap=cv2.VideoCapture(VideoPath)
num = 0
while True:
    ret, frame = cap.read()
    num += 1
    if not ret:
        break  # 取这一帧的图像失败, 就退出

    # 视频显示
    # cv2.imshow('frame',frame)
    # cv2.waitKey(1)

    # HSV转换
    HSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)  # 把BGR图像转换为HSV格式
    Lower = np.array([15, 150, 155])  # 要识别颜色的下限
    Upper = np.array([30, 255, 255])  # 要识别的颜色的上限
    # mask是把HSV图片中在颜色范围内的区域变成白色,其他区域变成黑色
    mask = cv2.inRange(HSV, Lower, Upper)
    # 视频显示
    # cv2.imshow('mask',mask)
    # cv2.waitKey(1)
    #下面四行是用卷积进行滤波
    kernel_2 = np.ones((2,2),np.uint8)#2x2的卷积核
    kernel_3 = np.ones((3,3),np.uint8)#3x3的卷积核
    kernel_4 = np.ones((4,4),np.uint8)#4x4的卷积核
    erosion = cv2.erode(mask,kernel_4,iterations = 1)
    erosion = cv2.erode(erosion,kernel_4,iterations = 1)
    dilation = cv2.dilate(erosion,kernel_4,iterations = 1)
    dilation = cv2.dilate(dilation,kernel_4,iterations = 1)
    #target是把原图中的非目标颜色区域去掉剩下的图像
    target = cv2.bitwise_and(frame, frame, mask=dilation)
    # # 视频显示
    # cv2.imshow('target',target)
    # cv2.waitKey(1)

24.5 图像几何变换

24.5.1 图像缩放—cv2.resize()

cv2.resize(src,dsize[,dst[,fx,fy[,interpolation]]]]) → dst
src: 输入图像
dst: 输出图像
dsize: Size类型,指定输出图像大小,如果它等于0:dsize = Size(round(fxsrc.cols), round(fysrc.rows))
fx: 沿水平方向的缩放系数,默认值0,等于0时:(double)dsize.width/src.cols
fy: 沿垂直方向的缩放系数,默认值0,等于0时:(double)dsize.height/src.rows
interpolation: 用于指定插值方式,默认为cv2.INTER_LINEAR (线性插值)

INTER_NEAREST 最邻近插值
INTER_LINEAR 线性插值(默认值)(放大图像使用–快)
INTER_AREA 区域插值(缩小图像推荐使用)
INTER_CUBIC 三次样条插值(放大图像使用–慢)
INTER_LANCZOS4 Lanczos插值

import cv2
import numpy as np
#图像的缩放

img1 = cv2.imread(r'F:/datas/fengjing1.png')
print(img1.shape)  # (546, 820, 3)
dst1 = cv2.resize(img1, None, fx=0.5, fy=0.2)
dst2 = cv2.resize(img1, (164, 109), interpolation=cv2.INTER_AREA) # 输出图像的形状:(w, h)

print(dst1.shape)  # (109, 410, 3)
print(dst2.shape)  # (109, 164, 3)
cv2.imshow('test', np.hstack([dst1, dst2]))
cv2.waitKey(0)
cv2.imshow('img1', img1)
cv2.waitKey(0)

#cv2.imwrite('test1.png', dst1)
#cv2.imwrite('test2.png', dst2)

通过变换步骤构建仿射变换的M矩阵

import cv2
import numpy as np
import math
#图像的缩放
img1 = cv2.imread(r'D:/datas2/snowqueen.png')# cv2.imread(r'datas/fengjing1.png')

'''
一、通过变换步骤构建仿射变换的M矩阵
'''
h, w, c = img1.shape

# 以中心点进行缩放
M1 = np.float64([[1,0,w // 2],[0,1, h // 2], [0, 0, 1]])
M2 = np.float64([[0.5,0,0],[0,0.5, 0], [0, 0, 1]])
M3 = np.float64([[1,0, -w // 2],[0,1, -h // 2], [0, 0, 1]])
M = M1 @ M2 @ M3 # 相对图像的中点缩放的反射矩阵

# 以原点进行缩放
# M = np.float64([[0.5,0,0],[0,0.5, 0], [0, 0, 1]])

dst1 = cv2.warpAffine(img1, M[:2], (w, h))
cv2.imshow('dst1_s', np.hstack([img1, dst1]))
cv2.waitKey(0)

24.5.2 图像平移

  1. 原理简介
    平移后,图像大小不变,信息丢失
    在这里插入图片描述
#---平移函数1(原图,X方向位移,Y方向位移)
#---不改变原图大小,会丢失信息
def imgTranslate(img,xOffset,yOffset):
    rows = img.shape[0]
    cols = img.shape[1]
    dst = np.zeros((rows,cols,3),np.uint8)
    for i in range (0,rows):
        for j in range(0,cols):
            x = j + xOffset 
            y = i + yOffset
            if(x>=0 and y >=0 and x<cols and y<rows):
                dst[y,x] = img[i,j]
    return dst

平移后,图像大小改变,信息完整
在这里插入图片描述

#---平移函数2(原图,x方向位移,Y方向位移)
#---改变原图大小,不丢失信息
def imgTranslate2(img,xOffset,yOffset):
    rows = img.shape[0]+abs (yOffset)
    cols = img.shape[1]+abs(xOffset)
    dst = np.zeros((rows,cols,3),np.uint8)
    for i in range(0,img.shape[0]):
        for j in range(0,img.shape[1]):
            x = j + xOffset 
            y = i + yOffset
    if(x>=0 and y>=0 and x<cols and y<rows):
        dst[y,x] = img[i,j]
    return dst
  1. 简单快速平移方法
    平移就是将对象换一个位置。如果你要沿(x,y)方向移动,移动的距离是(tx,ty),你可以以下面的方式构建移动矩阵∶
    M = [ 1 0 t x 0 1 t y ] M\text{=}\left[ \begin{matrix}1 & 0 & {{t}_{x}} \\0 & 1 & {{t}_{y}} \\\end{matrix} \right] M=[1001txty]
    你可以使用Numpy 数组构建这个矩阵(数据类型是np.float32),然后把它传给函数 cv2.warpAffine()

cv2.warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None) → dst
src: 输入图像 dst:输出图像
M: 2×3的变换矩阵
dsize: 变换后输出图像尺寸
flag: 插值方法
borderMode: 边界像素外扩方式
borderValue: 边界像素插值,默认用0填充
变换矩阵M可通过cv2.getAffineTransfrom(points1, points2)函数获得

import cv2
import numpy as np
#图像的缩放
img1 = cv2.imread(r'F:/datas/fengjing1.png')
print(img1.shape)
dst1 = cv2.resize(img1, None, fx=0.2, fy=0.2)
dst2 = cv2.resize(img1, (164, 109), interpolation=cv2.INTER_AREA) # 输出图像的形状:(w, h)

M = np.float64([[1,0,50],[0,1, 50]])
h, w, c = img1.shape
#平移
dst = cv2.warpAffine(img1, M, (w+100, h+100))
cv2.imshow('dst',  dst)
cv2.waitKey(0)

通过解方程组,获取M

# 根据3组坐标解方程,(2行3列总共6个未知数,需要6组方程)
# 解方程方法:cv2.getAffineTransform(src, dst)
src = np.array([[0, 0], [200, 0], [0, 200]], np.float32)
dst = np.array([[0, 0], [100, 0], [0, 100]], np.float32)
M = cv2.getAffineTransform(src, dst)# 通过解方程组,获取M
dst = cv2.warpAffine(img1, M, (w, h))
cv2.imshow('getAffine', np.hstack([img1, dst]))
cv2.waitKey(0)

24.5.3 图像旋转

  1. 图示简介

在这里插入图片描述
OpenCV没有提供直接旋转图像的函数,图像旋转可能会造成图像信息丢失,图像旋转可以用仿射变换来实现,主要用到函数:cv2.getRotationMatrix2D()cv2.warpAffine()

M = [ cos ⁡ θ − sin ⁡ θ sin ⁡ θ cos ⁡ θ ] ⇒ [ α β ( 1 − α ) ⋅ c e n t e r ( x ) − β ⋅ c e n t e r ( y ) − β α β ⋅ c e n t e r ( x ) + ( 1 − α ) ⋅ c e n t e r ( y ) ] M=\left[ \begin{matrix}\cos \theta & -\sin \theta \\ \sin \theta & \cos \theta \\\end{matrix} \right]\Rightarrow \left[ \begin{matrix} \alpha & \beta & (1-\alpha )\cdot center(x)-\beta \cdot center(y) \\ -\beta & \alpha & \beta \cdot center(x)+(1-\alpha )\cdot center(y) \\\end{matrix} \right] M=[cosθsinθsinθcosθ][αββα(1α)center(x)βcenter(y)βcenter(x)+(1α)center(y)]

cv2.getRotationMatrix2D(center,angle,scale) → retval
旋转中心,旋转角度,旋转后的缩放因子

import cv2
import numpy as np
#图像的缩放
img1 = cv2.imread(r'F:/datas/fengjing1.png')
print(img1.shape)
dst1 = cv2.resize(img1, None, fx=0.2, fy=0.2)
dst2 = cv2.resize(img1, (164, 109), interpolation=cv2.INTER_AREA)# 输出图像的形状:(w, h)

M = np.float64([[1,0,50],[0,1, 50]])
h, w, c = img1.shape

#旋转
M = cv2.getRotationMatrix2D((w/2, h/2), 90, 0.6)
dst = cv2.warpAffine(img1, M, (w, h))
cv2.imshow('dst', np.hstack([img1, dst]))
cv2.waitKey(0)

通过变换步骤构建仿射变换的M矩阵

import cv2
import numpy as np
import math

img1 = cv2.imread(r'D:/datas2/snowqueen.png')# cv2.imread(r'datas/fengjing1.png')
h, w, c = img1.shape
'''============================================='''
# 旋转 逆时针30度——原点
rad = 30/180*math.pi
# 旋转 顺时针30度——原点
rad = -30/180*math.pi

M = np.float64([[math.cos(rad),math.sin(rad),0],[-math.sin(rad),math.cos(rad), 0], [0, 0, 1]])
dst1 = cv2.warpAffine(img1, M[:2], (w, h))
cv2.imshow('dst1_r', np.hstack([img1, dst1]))
cv2.waitKey(0)
'''============================================='''
# 以中点为中心点逆时针旋转30度
rad = 30/180*math.pi
M1 = np.float64([[1,0,w // 2],[0,1, h // 2], [0, 0, 1]])
M2 = np.float64([[math.cos(rad),math.sin(rad),0],[-math.sin(rad),math.cos(rad), 0], [0, 0, 1]])
M3 = np.float64([[1,0, -w // 2],[0,1, -h // 2], [0, 0, 1]])
M = M1 @ M2 @ M3
dst2 = cv2.warpAffine(img1, M[:2], (w, h))
cv2.imshow('dst1_r', np.hstack([img1, dst2]))
cv2.waitKey(0)
'''============================================='''
# 以中点为中心点缩放0.8,然后逆时针旋转30度
#1
rad = 30/180*math.pi
M1 = np.float64([[1,0,w // 2],[0,1, h // 2], [0, 0, 1]])
M2_1 = np.float64([[math.cos(rad),math.sin(rad),0],[-math.sin(rad),math.cos(rad), 0], [0, 0, 1]])
M2_2 = np.float64([[0.8,0,0],[0,0.8, 0], [0, 0, 1]])
M3 = np.float64([[1,0, -w // 2],[0,1, -h // 2], [0, 0, 1]])
M = M1 @ (M2_1 @ M2_2) @ M3#
dst2 = cv2.warpAffine(img1, M[:2], (w, h))
cv2.imshow('dst1_r', np.hstack([img1, dst2]))
cv2.waitKey(0)
#2
M = cv2.getRotationMatrix2D((w/2, h/2), 30, 0.8)
dst = cv2.warpAffine(img1, M, (w, h))
cv2.imshow('all', np.hstack([img1, dst]))
cv2.waitKey(0)
  1. 插值算法——最近邻算法
    仿射变换之后的坐标为 ( x ^ , y ^ ) (\hat{x},\hat{y}) (x^,y^),之后的为 ( x , y ) (x,y) (x,y),获取到值的函数为f: f i n p u t ( x , y ) {{f}_{input}}(x,y) finput(x,y) f o u t p u t ( x ^ , y ^ ) {{f}_{output}}(\hat{x},\hat{y}) foutput(x^,y^)

24.5.4 转置和镜像

用到的函数cv2.transpose()cv2.flip(),可以实现转置和镜像变换,以及90°,180°旋转

cv2.transpose(src[,dst]) → dst 行列互换

cv2.flip(src[,dst]) → dst 对称变化
flipCode = 0, 垂直翻转(沿X轴翻转);
flipCode > 0, 水平翻转(沿Y轴翻转);
flipCode < 0, 水平垂直翻转(180°中心)

import cv2
import numpy as np

#转置和镜像
img = cv2.imread(r'F:/datas/snowqueen.png')
dst = cv2.transpose(img)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.imshow('transpose', dst)
cv2.waitKey(0)
dst1 = cv2.flip(img, 0)  # img(:,::-1)
dst2 = cv2.flip(img, 1)  # img(::-1,:)
dst3 = cv2.flip(img, -1)  # img(::-1,::-1)
cv2.imshow('flip', np.hstack([img, dst1, dst2, dst3]))
cv2.waitKey(0)

24.5.5 重映射—cv2.remap()

cv2.remap()函数
重映射是指把一个图像中的一个位置的像素通过映射关系转换到另一图像的指定位置。对于输入原图像f(x, y), 目标图像g(x, y), 映射关系为T, 则满足下式:g(x, y) = T(f(x, y))

cv2.remap(src, map1, map2, interpolation[,dst[,borderMode[, borderValue]]]) → dst
map1: 表示(x, y)点的坐标或x坐标,CV_16SC2, CV_32FC1,CV_32FC2类型
map2: 表示(x, y)点y坐标,如果map1为(x, y),map2可以选择不用,可以是CV_16UC1, CV_32FC1
interpolation: 表示插值方法
borderMode: 表示边界插值类型
borderValue: 表示插值数值

import math
import cv2
import numpy as np

# 重映射
img = cv2.imread(r'F:/datas/snowqueen.png')
xMap = np.zeros(img.shape[:2],np.float32)
yMap = np.zeros(img.shape[:2],np.float32)
rows = img.shape [0]
cols = img.shape [1]
for i in range(0, rows):
    for j in range (0, cols):
        xMap.itemset((i,j),j)
        yMap.itemset((i,j),i + 5*math.sin(j/10.0))
        
dst = cv2.remap(img,xMap,yMap,interpolation=cv2.INTER_LINEAR)
cv2.imshow('dst',  dst)
cv2.waitKey(0)

24.5.6 透视变换——图像校正

import numpy as np
import cv2
from matplotlib import pyplot as plt
img_src = cv2.imread(r'D:/datas2/imageTextR.png')# chessboard-2.png
img = img_src.copy()
h, w, _ = img_src.shape

#手动捕捉特征点:
src = np.float32([[30,90],[508,12],[45,300]])
dst = np.float32([[30,90],[516,66],[7,305]]) 
M = cv2.getAffineTransform(src, dst)
affineResult = cv2.warpAffine(img, M, (w,h))
cv2.imshow('compare',np.hstack([img,affineResult]))
cv2.waitKey(0)

在这里插入图片描述

24.6 OpenCV界面事件操作

utils工具包:

# encoding:utf8
import numpy as np
import cv2
from matplotlib import pyplot as plt
class Mouse:
    def __init__(self, img, winname='src'):
        self.img = img
        self.points = []# 保存每次鼠标单击的位置
        self.winname = winname
    def selectPoints(self, color):
        
        self.color = color
        cv2.namedWindow(self.winname)
        #绑定鼠标事件
        cv2.setMouseCallback(self.winname, self.onMouse)
        #打开窗口进行标记
        show_img(self.winname, self.img)
    #鼠标事件的回调函数    
    def onMouse(self, event, x, y, flags, userdatas=0):
        '''
        event:鼠标事件,可以通过[i for i in dir(cv2) if i.startswith('EVENT')]查看
        x,y:当前鼠标的位置
        flags:鼠标事件标记,可以通过[i for i in dir(cv2) if i.startswith('EVENT_FLAG')]查看
        userdatas:额外的参数
        '''
        #print('onMouse')
        if event == cv2.EVENT_LBUTTONDOWN and len(self.points) != 4:# 单击鼠标事件
            point = [x, y]
            self.points.append(point)
            self.drawSelectPoints()
    def drawSelectPoints(self):
        cv2.drawMarker(self.img, self.points[-1], self.color,
                       markerType=cv2.MARKER_STAR, markerSize=4)
        font = cv2.FONT_HERSHEY_COMPLEX
        cv2.putText(self.img, str(len(self.points)), self.points[-1], font, 1, self.color)
        cv2.imshow(self.winname, self.img)
        k = cv2.waitKey(0)
        if k == ord('q'):
            cv2.destroyAllWindows()
def show_img(winname, img):
    cv2.imshow(winname, img)
    cv2.waitKey(0)
    
def show_imgs(groups):# 展示多组对比图
    nrows = len(groups)
    fig, axes = plt.subplots(nrows, 2)
    for i, group in enumerate(groups):
        axes[i][0].set_title('before')
        axes[i][0].imshow(group[0])
        axes[i][1].set_title('after')
        axes[i][1].imshow(group[1])
    plt.show()
    
    
    

24.6.1 鼠标操作

指定鼠标操作消息回调函数,setMouseCallback(),原型如下:cv2.setMouseCallback()

CV_EXPORTS void setMouseCalback(const sring& winmname, MouseCallack onMouse, void* userdata=0);
winname——窗口名
onMouse——鼠标事件时被调用的函数指针,原型形式:void Fun(int event, int x, int y, int flags, void* param);
userdata——用户定义传到回调函数的参数,默认值0

event:
CV_EVENT_MOUSEMOVE = 0
CV_EVENT_LBUTTONDOWN = 1
CV_EVENT_RBUTTONDOWN = 2
CV_EVENT_MBUTTONDOWN = 3
CV_EVENT_LBUTTONNUP = 4
CV_EVENT_RBUTTONNUP = 5
CV_EVENT_MUTTONNUP = 6
CV_EVENT_LBUTTONDBLCLK = 7
CV_EVENT_RBUTTONDBLCLK = 8
CV_EVENT_MBUTTONDBLCLK = 9

flags:
CV_EVENT_FLAG_LBUTTON = 1
CV_EVENT_FLAG_RBUTTON = 2
CV_EVENT_FLAG_MBUTTON = 4
CV_EVENT_FLAG_CTRLKEY = 8
CV_EVENT_FLAG_SHIFTKEY = 16
CV_EVENT_FLAG_ALTKEY = 32

手动变换

import numpy as np
import cv2
from utils import Mouse, show_img, show_imgs
import math
from matplotlib import pyplot as plt
img_src = cv2.imread(r'D:/datas2/imageTextR.png')# chessboard-2.png
img = img_src.copy()
h, w, _ = img_src.shape

#手动捕捉特征点:
src = np.float32([[30,90],[508,12],[45,300]])
dst = np.float32([[30,90],[516,66],[7,305]]) 
M = cv2.getAffineTransform(src, dst)
affineResult = cv2.warpAffine(img, M, (w,h))
cv2.imshow('compare',np.hstack([img,affineResult]))
cv2.waitKey(0)

鼠标点击——三个点

import numpy as np
import cv2
from utils import Mouse, show_img, show_imgs
import math
from matplotlib import pyplot as plt
img_src = cv2.imread(r'D:/datas2/imageTextR.png')# chessboard-2.png
img = img_src.copy()
h, w, _ = img_src.shape

#通过鼠标标记位置
colors = [[0, 255, 0], [0, 0, 255]]
winnames = ['src', 'dst']
mouse_src = Mouse(img, winnames[0])
mouse_src.selectPoints(colors[0])# 绑定鼠标事件
src = np.float32(mouse_src.points)# 矫正之前的位置
mouse_dst = Mouse(img, winnames[1])
mouse_dst.selectPoints(colors[1])# 绑定鼠标事件
dst = np.float32(mouse_dst.points)# 矫正之后的位置
# 获取仿射变换矩阵M(2,3)
M = cv2.getAffineTransform(src[:3], dst[:3])
affineResult = cv2.warpAffine(img, M, (img.shape[1]+120, img.shape[0]+150))

# 获取透视变换/投影变换的矩阵M(3,3)
M = cv2.getPerspectiveTransform(src, dst)
# 进行透视变换
perspectiveResult = cv2.warpPerspective(img_src, M, (w, h))
# 展示图片
show_imgs([[img, affineResult], [img, perspectiveResult]])
#cv2.imwrite('图片矫正结果.png', perspectiveResult)
#cv2.imshow('perspectiveResult', perspectiveResult)
#cv2.waitKey(0)
#cv2.destroyAllWindows()

24.6.2 鼠标操作+透视变换

鼠标点击——4个点

import numpy as np
import cv2
from utils import Mouse, show_img, show_imgs
import math
from matplotlib import pyplot as plt
img_src = cv2.imread(r'D:/datas2/chessboard-2.png')# chessboard-2.png
img = img_src.copy()
h, w, _ = img_src.shape

#通过鼠标标记位置
colors = [[0, 255, 0], [0, 0, 255]]
winnames = ['src', 'dst']
mouse_src = Mouse(img, winnames[0])
mouse_src.selectPoints(colors[0])# 绑定鼠标事件
src = np.float32(mouse_src.points)# 矫正之前的位置
mouse_dst = Mouse(img, winnames[1])
mouse_dst.selectPoints(colors[1])# 绑定鼠标事件
dst = np.float32(mouse_dst.points)# 矫正之后的位置
# 获取仿射变换矩阵M(2,3)
M = cv2.getAffineTransform(src[:3], dst[:3])
affineResult = cv2.warpAffine(img, M, (img.shape[1]+120, img.shape[0]+150))

# 获取透视变换/投影变换的矩阵M(3,3)
M = cv2.getPerspectiveTransform(src, dst)
# 进行透视变换
perspectiveResult = cv2.warpPerspective(img_src, M, (w, h))
# 展示图片
show_imgs([[img, affineResult], [img, perspectiveResult]])

24.7 图像滤波

24.7.1 图像滤波简介

滤波实际上是信号处理的一个概念,图像可以看成一个二维信号,其中像素点灰度值得高低代表信号的强弱
高频: 图像中变化剧烈的部分
低频: 图像中变化缓慢,平坦的部分

根据图像高低频特性,设置高通和低通滤波器。高通滤波可以检测图像中尖锐、变化明显的地方,低通滤波可以让图像变得平滑,消除噪声干扰
图像滤波是OpenCV图像处理的重要部分,在图像预处理方面应用广泛,图像滤波的好坏决定着后续处理的结果好坏
本节主要介绍低通滤波部分(图像平滑去噪),高通部分(边缘检测)后续介绍

本节主要介绍的图像滤波函数方法:
线性滤波: 方框滤波、均值滤波、高斯滤波
非线性滤波: 中值滤波、双边滤波

邻域算子:利用给定像素周围的像素值决定此像素的最终输出值的一种算子
线性滤波:一种常用的邻域算子,像素输出取决于输入像素的加权和
在这里插入图片描述
g ( i , j ) = ∑ k , l f ( i + k , j + l ) ⋅   h ( k , l ) g(i,j)=\sum\limits_{k,l}{f(i+k,j+l)\cdot }\text{ }h(k,l) g(i,j)=k,lf(i+k,j+l) h(k,l)
线性滤波器输出像素 g ( i , j ) g(i,j) g(i,j) 是输入像素 f ( i + k , j + l ) f(i+k,j+l) f(i+k,j+l) 的加权和,其中 h ( k , l ) h(k,l) h(k,l) 我们称之为核,是滤波器的加权系数,上面式子简写为:
g = f ⊗ h g=f\otimes h g=fh

核个数 = 输出通道数
输入通道数 = 每个核的通道数
输入通道数 ≠ 输出通道数

24.7.2 方框滤波——cv2.boxFilter()

方框滤波用到的核:
h = α [ 1 1 … 1 1 1 … 1 ⋮ ⋮ ⋱ ⋮ 1 1 … 1 ] h=\alpha \left[ \begin{matrix} 1 & 1 & \ldots & 1 \\ 1 & 1 & \ldots & 1 \\ \vdots & \vdots & \ddots & \vdots \\ 1 & 1 & \ldots & 1 \\\end{matrix} \right] h=α111111111,其中: α = { 1 h s i z e . w i d t h ⋅ h s i z e . h e i g h t n o r m a l i z e = t r u e 1 o t h e r w i s e \alpha =\left\{ \begin{matrix} \frac{1}{hsize.width\cdot hsize.height} & normalize=true \\ 1 & otherwise \\\end{matrix} \right. α={hsize.widthhsize.height11normalize=trueotherwise
当normalize为true时,方框滤波也就成了均值滤波。也就是说均值滤波是方框滤波归一化后的特殊情况。归一化就是将要处理的量缩放到一定范围,比如(0, 1)。

cv2.boxFilter(src,ddepth, ksize[,dst[anchor[,normalize[, borderType]]]]) → dst
ddepth: 输出图像的深度, -1代表使用原图像深度,即src.depth()(卷积核的个数)
ksize: Size类型表示内核大小,一般用Size(w,h)表示内核大小, Size(3,3)表示3x3的核大小
anchor: 表示锚点(即被平滑的那个点), 默认值Point(-1, -1),表示锚点在核中心
normalize: 默认值true, 标识符, 表示内核是否被归一化
borderType: 图像像素边界模式,一般用默认值即可

24.7.3 均值滤波——cv2.blur()

均值滤波用到的核:
h = K w i d t h ⋅ K h e i g h t [ 1 1 … 1 1 1 … 1 ⋮ ⋮ ⋱ ⋮ 1 1 … 1 ] h=\frac{{}}{{{K}_{width}}\cdot {{K}_{height}}}\left[ \begin{matrix} 1 & 1 & \ldots & 1 \\ 1 & 1 & \ldots & 1 \\ \vdots & \vdots & \ddots & \vdots \\ 1 & 1 & \ldots & 1 \\ \end{matrix} \right] h=KwidthKheight111111111
均值滤波即方框滤波归一化特例,就是用邻域内像素均值来代替该点像素值,均值滤波在去噪的同时也破坏了图像细节部分

cv2.blur(src,ksize[,dst[,anchor[,borderType]]]) → dst
ksize: Size类型表示内核大小,一般用Size(w,h)表示内核大小, Size(3,3)表示3x3的核大小
anchor: 表示锚点(即被平滑的那个点), 默认值Point(-1, -1),表示锚点在核中心
borderType: 图像像素边界模式,一般用默认值即可

24.7.4 高斯滤波——cv2.GaussianBlur()

高斯滤波器被称为最有用的滤波器,每个像素点都是由本身和邻域内的其他像素值经过加权平均后得到的, 加权系数越靠近中心越大, 越远离中心越小, 能够很好的滤除噪声。
在这里插入图片描述

cv2.GaussianBlur(src,ksize,sigmaX[,dst[,sigmaY[,borderType]]]) → dst
ksize: 高斯内核大小,一般用Size(w,h)表示内核大小, w, h可以不同, 但是必须为正奇数或者0, 由sigma计算得来
sigmaX: 表示高斯函数在X方向上的标准偏差
sigmaY: 表示高斯函数在Y方向上的标准偏差, 若sigmaY=0, 就将它设置为sigmaX,若sigmaY=0 && sigmaX=0则由ksize.widthksize.height计算出来
borderType: 图像像素边界模式,一般用默认值即可

24.7.5 中值滤波——cv2.medianBlur()

中值滤波是一种非线性滤波, 是用像素点邻域灰度值的中值代替该点的灰度值, 可以去除脉冲噪声和椒盐噪声
median({1,2,3,3,7,5,1,8})=3 排序后的中间那个值

cv2.medianBlur(src,ksize[,dst]) → dst
ksize: int类型的孔径的线性尺寸, 大于1的奇数

适用于:椒盐噪声

24.7.5 双边滤波——cv2.bilateralFilter()

双边滤波是一种非线性滤波, 是结合图像空间邻近度和像素值相似度的一种折中处理, 尽量在去噪同时保存边缘

cv2.bilateralFilter(src,d, sigmaColor,sigmaSpace[,dst[, borderTypel] → dst
d: 表示过滤过程中每个像素的邻域直径
sigmaColor: 颜色空间滤波器sigma值, 值越大表面该像素邻域内有越广泛的颜色会混到一起,产生较大的半相等颜色区域
sigmaSpace: 坐标空间中滤波器的sigma值, 坐标空间的标准方差
borderType: 图像像素边界模式,一般用默认值即可

24.8 图像阈值化

24.8.1 图像阈值化简介

图像阈值化是图像处理的重要基础部分, 应用很广泛, 可以根据灰度差异来分割图像不同部分。阈值化处理的图像一般为单通道图像(灰度图) ;阈值化参数的设置可以使用滑动条来debug;阈值化处理易光照影响, 处理时应注意

本节主要介绍的图像阈值化函数方法:
固定阈值: cv2.threshold()
自适应阈值: cv2.adaptiveThreshold()

24.8.2 固定阈值——cv2.threshold()

给定阈值进行阈值操作得到二值图(0, 1两类值), 可以过滤灰度值过大或过小的点

cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
src: 单通道图像(灰度图或二值图)
dst: 输出图像要求和src一样的尺寸和类型
thresh: 给定的阈值
maxval: 第五个参数设置为CV_THRESH_BINARYCV_THRESH_BINARY_INV 阈值类型的最大值

二值化阈值处理
THRESH_BINARY: d s t ( x , y ) = { m a x V a l i f   s r c ( x , y ) > t h r e s h 0 o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} maxVal \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\0\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={maxValifsrc(x,y)>thresh0otherwise
反二值化阈值处理
THRESH_BINARY_INV: d s t ( x , y ) = { 0 i f   s r c ( x , y ) > t h r e s h m a x V a l o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} 0 \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\maxVal\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={0ifsrc(x,y)>threshmaxValotherwise
截断阈值化处理
THRESH_TRUNC: d s t ( x , y ) = { t h r e s h o l d i f   s r c ( x , y ) > t h r e s h s r c ( x , y ) o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} threshold \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\src(x,y)\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={thresholdifsrc(x,y)>threshsrc(x,y)otherwise
超阈值零处理
THRESH_TOZORE: d s t ( x , y ) = { s r c ( x , y ) i f   s r c ( x , y ) > t h r e s h 0 o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} src(x,y) \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\0\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={src(x,y)ifsrc(x,y)>thresh0otherwise
低阈值零处理
THRESH_TOZORE_INV: d s t ( x , y ) = { 0 i f   s r c ( x , y ) > t h r e s h s r c ( x , y ) o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} 0 \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\src(x,y)\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={0ifsrc(x,y)>threshsrc(x,y)otherwise

在这里插入图片描述

24.8.3 自适应阈值——cv2.adaptiveThreshold()

对矩阵采用自适应阈值操作, 自适应阈值是根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值,把局部的均值作为局部的阈值

cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) → dst
src: 单通道图像(灰度图或二值图)
dst: 输出图像要求和src一样的尺寸和类型
maxValue: 使用cv2.THRESH_BINARY cv2.THRESH_BINARY_INV 的最大值
adaptiveMethod: 指定自适应阈值算法, 可取值为cv2.ADAPTIVE_THRESH_MEAN_Ccv2.ADAPTIVE_THRESH_GAUSSIAN_C
thresholdType: 取阈值类型取值必须为cv2.THRESH_BINARYcv2.THRESH_BINARY_INV二者之一
blockSize: 用来计算阈值的邻域大小3, 5, 7,…
C: 减去平均或加权平均后的常数值

函数 adaptiveThreshold 将灰度图像变换到二值图像,采用下面公式:
thresholdType= cv2.THRESH_BINARY :
d s t ( x , y ) = { m a x V a l   i f   s r c ( x , y ) > t h r e s h 0   o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} maxVal\text{ }if\text{ }src(x,y)>thresh \\ 0\text{ }otherwise \\ \end{matrix} \right. dst(x,y)={maxVal if src(x,y)>thresh0 otherwise
thresholdType= cv2.THRESH_BINARY_INV:
d s t ( x , y ) = { 0 i f   s r c ( x , y ) > t h r e s h m a x V a l o t h e r w i s e dst(x,y)=\left\{ \begin{matrix} 0 \mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}if\overset{{}}{\mathop{{}}}\,src(x,y)>thresh \\maxVal\mathop{{}}_{{}}^{{}}\mathop{{}}_{{}}^{{}}otherwise \\\end{matrix} \right. dst(x,y)={0ifsrc(x,y)>threshmaxValotherwise
其中T(x, y)为分别计算每个单独像素的阈值, 取值如下:
对方法 cv2.ADAPTIVE_THRESH_MEAN_C, 先求出块中的均值,再减掉C
对方法 cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 先求出块中的加权和(gaussian), 再减掉C

import numpy as np
import cv2
image_path = r'D:/datas2/yellowmoon.png'
img = cv2.imread(image_path)

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

img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('img_gray', img_gray)
cv2.waitKey(0)
thresh = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 5, 6)
# _, thresh = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imshow('thresh', thresh)
cv2.waitKey(0)

在这里插入图片描述

24.8.4 Otsu阈值化

在使用全局阈值时,我们就是随便给了一个数来做阈值,那我们怎么知道我们选取的这个数的好坏呢?答案就是不停的尝试。如果是一副双峰图像(简单来说双峰图像是指图像直方图中存在两个峰)呢?我们岂不是应该在两个峰之间的峰谷选一个值作为阈值?这就是 Otsu 二值化要做的。简单来说就是对一副双峰图像自动根据其直方图计算出一个阈值。(对于非双峰图像,这种方法得到的结果可能会不理想)

这里用到到的函数还是 cv2.threshold(),但是需要多传入一个参数(flag): cv2.THRESH_OTSU。这时要把阈值设为 0。然后算法会找到最优阈值,这个最优阈值就是返回值 retVal。如果不使用 Otsu 二值化,返回的 retVal 值与设定的阈值相等

在这里插入图片描述

cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]]) ->hist
通过直方图可以很好的对整幅图像的灰度分布有一个整体的了解,直方图的x轴是灰度值(0~255),y轴是图片中具有同一个灰度值的点的数目。而calcHist()函数则可以帮助我们统计一幅图像的直方图
mages: 原图像图像格式为 uint8 或 float32。当传入函数时应 用中括号 [] 括来例如[img]
channels: 同样用中括号括来它会告函数我们统幅图 像的直方图。如果入图像是灰度图它的值就是 [0]; 如果是彩色图像的传入的参数可以是 [0][1][2] 它们分别对应着 BGR。
mask: 掩模图像。统整幅图像的直方图就把它为 None。但是如果你想统图像某一分的直方图的你就制作一个掩模图像并使用它。
histSize: BIN 的数目。也应用中括号括来
ranges: 像素值范围常为 [0 256]

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread(r'D:/datas2/snowqueen.png', 0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])

print(hist)
print(hist[175])
print(type(hist))

plt.subplot(131)
plt.imshow(img,cmap='gray')
plt.xticks([])
plt.yticks([])

plt.subplot(132)
plt.hist(img.ravel(),256)
plt.xticks([])
plt.yticks([])

plt.subplot(133)
plt.plot(hist,color = 'red')
plt.xticks([])
plt.yticks([])
plt.show()

在这里插入图片描述

24.8.5 全局阈值——threshold

  1. 手动滑动条调试
  2. 不断测试(debug)
  3. Otsu算法:根据统计直方图分析获取
import cv2
import numpy as np
from matplotlib import pyplot as plt

#img = cv2.imread('grey-gradient.jpg', 0)#
img = cv2.imread(r'D:/datas2/snowqueen.png', 0)
ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

在这里插入图片描述

import cv2
import numpy as np
from matplotlib import pyplot as plt

#img = cv2.imread('grey-gradient.jpg', 0)#
img = cv2.imread(r'D:/datas2/snowqueen.png', 0)
ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC+cv2.THRESH_OTSU)
ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO+cv2.THRESH_OTSU)
ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV+cv2.THRESH_OTSU

titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

在这里插入图片描述

24.9 图像形态学操作

膨胀腐蚀概述

  1. 膨胀、腐蚀属于形态学的操作, 简单来说就是基于形状的一系列图像处理操作
  2. 膨胀腐蚀是基于高亮部分(白色)操作的, 膨胀是対高亮部分进行膨胀, 类似“领域扩张”, 腐蚀是高亮部分被腐蚀, 类似“领域被蚕食”
  3. 膨胀腐蚀的应用和功能:
    消除噪声
    分割独立元素或连接相邻元素
    寻找图像中的明显极大值、极小值区域
    求图像的梯度
  4. 其他相关:
    开运算、闭运算
    顶帽、黒帽
    形态学梯度

形态学其他操作:
开运算、闭运算、顶帽、黒帽、形态学梯度
基于膨胀腐蚀基础, 利用cv2.morphologyEx()函数进行操作
核心函数:

cv2.morphologyEx(src,op, kernel[, dst[,anchor[,iterations[, borderType[, borderValue]]]]]) → dst
src: 输入原图像
op: 表示形态学运算的类型, 可以取如下值:

cv2.MORPH_GRADIENT
cv2.MORPH_ERODE
cv2.MORPH_DILATE
cv2.MORPH_OPEN
cv2.MORPH_CLOSE
cv2.MORPH_TOPHAT
cv2.MORPH_BILACKHAT

kernel: 形态学运算内核, 若为NULL, 表示使用参考点位于中心的3x3内核, 一般使用getStruecuringElement函数获得
dst: 输出图像要求和 src 一样的尺寸和类型
anchor: 锚的位置, 默认值Point(-1,-1), 表示位于中心
interations: 迭代使用函数的次数, 默认为1
borderType: 边界模式, 一般采用默认值
borderValue: 边界值, 一般采用默认值

24.9.1 膨胀——cv2.dilate()

膨胀就是求局部最大值的操作, 从数学角度上来讲, 膨胀或腐蚀就是将图像(或区域)A与 核B进行卷积。
核可以是任意大小和形状, 它有一个独立定义的参考点(锚点), 多数情况下, 核是一个小的中间带参考点和实心正方形或者圆盘, 可以看做是一个模板或掩码。

cv2.getStructuringElement(shape, ksize, point)
shape: MORPH_RECT=0.MORPH_CROSS=1,MORPH_ELLIPSE=2
ksize和point(默认值为中心)分别表示内核尺寸和锚点位置

import cv2
import numpy as np

kernel = np.ones((3, 3), np.uint8)
# MORPH_CROSS
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
print('kernel:\n',kernel)
'''
kernel:
 [[0 1 0]
 [1 1 1]
 [0 1 0]]
'''
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,3))
print('kernel:\n',kernel)
'''
kernel:
 [[0 0 1 0 0]
 [1 1 1 1 1]
 [0 0 1 0 0]]
'''
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(4,3))
print('kernel:\n',kernel)
'''
kernel:
 [[0 0 1 0]
 [1 1 1 1]
 [0 0 1 0]]
'''

# MORPH_ELLIPSE
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(7,7))
print('kernel:\n',kernel)
'''
kernel:
 [[0 0 0 1 0 0 0]
 [0 1 1 1 1 1 0]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [0 1 1 1 1 1 0]
 [0 0 0 1 0 0 0]]
'''
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(7,5))
print('kernel:\n',kernel)
'''
kernel:
 [[0 0 0 1 0 0 0]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [0 0 0 1 0 0 0]]
'''

# MORPH_RECT
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(7,5))
print('kernel:\n',kernel)
'''
kernel:
 [[1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]]
'''

膨胀是求局部最大值的操做, 核B与图形卷积, 即核B覆盖的区域的像素点的最大值, 并把这个最大值复制给参考点指定的像素, 这样就会使图像中的高亮区域逐渐增长, 如下图
在这里插入图片描述

cv2.dilate(src, kernel, dst,[ anchor[, iterations[, borderType[, borderValue]]]]]) → dst
src: 输入原图像(建议为二值图)
dst: 输出图像要求和src一样的尺寸和类型
kernel: 膨胀操作的核, 当为NULL时, 表示使用参考点位于中心的3x3的核,一般使用cv2.getStructuringElement获得指定形状和尺寸的结构元素(核),可选: 矩形、交叉形、椭圆形
anchor: 锚的位置, 默认值Point(-1,-1), 表示位于中心
interations: 膨胀的次数
borderType: 边界模式, 一般采用默认值
borderValue: 边界值, 一般采用默认值

import cv2
import numpy as np

img = cv2.imread('D:/datas2/j.png', 0)
cv2.imshow('j.png', img)
print(img.shape)

kernel = np.ones((7, 7), np.uint8)
kernel17 = np.ones((1, 7), np.uint8)
kernel71 = np.ones((7, 1), np.uint8)
dilation = cv2.dilate(img, kernel, iterations=1)
dilation17 = cv2.dilate(img, kernel17, iterations=1)
dilation71 = cv2.dilate(img, kernel71, iterations=1)
cv2.imshow('dilation', np.hstack([img, dilation, dilation17, dilation71]))
cv2.moveWindow('dilation', x=img.shape[1], y=0)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

24.9.2 腐蚀——cv2.erode()

腐蚀和膨胀相反, 是取局部最小值, 高亮区域逐渐减小, 如下图所示:
在这里插入图片描述

cv2.erode(src, kernel[, dst[,anchor[,iterations[, borderType[, borderValue]]]]]) → dst

import cv2
import numpy as np

img = cv2.imread(r'D:/datas2/j.png', 0)
cv2.imshow('j.png', img)
print(img.shape)

kernel = np.ones((5, 5), np.uint8)
# cv2.getStructuringElement(shape, ksize)
erosion = cv2.erode(img, kernel, iterations=1)
dilation = cv2.dilate(img, kernel, iterations=1)
print(img.shape, erosion.shape, dilation.shape)
cv2.imshow('erode', np.hstack([img, erosion, dilation]))
cv2.moveWindow('erode', x=img.shape[1], y=0)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

import cv2
import numpy as np

img = cv2.imread(r'D:/datas2/j2.png', 0)
cv2.imshow('j2.png', img)
cv2.waitKey(0)
print(img.shape)

kernel = np.ones((3, 3), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)  # 腐蚀
dilation = cv2.dilate(img, kernel, iterations=1)  # 膨胀

cv2.imshow('erosion&dilation', np.hstack([img,erosion,dilation]))
cv2.waitKey(0)

在这里插入图片描述

24.9.3 开运算(open)

开运算是先腐蚀后膨胀的过程, 开运算可以用来消除小物体, 在纤细点处分离物体, 并在平滑较大物体边界的同时不明显的改变其面积。

opening= cv2.morphologyEx(img,cv2.MORPH_OPEN,k1)

import cv2
import numpy as np

img = cv2.imread(r'D:/datas2/j2.png', 0)
kernel = np.ones((3, 3), np.uint8)
# 开运算:先腐蚀再膨胀就叫做开运算。就像我们上介绍的样,它用来去噪声。
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

cv2.imshow('opening', opening)
cv2.moveWindow('opening', x=img.shape[1], y=0)
cv2.waitKey(0)

在这里插入图片描述

24.9.4 闭运算(close)

闭运算是先膨胀后腐蚀的过程, 闭运算可以用来消除小型黑洞(黑色区域)。

closing = cv2.morphologyEx(img,cv2.MORPH_CLOSE,k1)

import cv2
import numpy as np

img = cv2.imread(r'D:/datas2/j2.png', 0)
kernel = np.ones((3, 3), np.uint8)
# 闭运算
# 先膨胀再腐蚀。它经常用来填充前景物体中的小洞或者前景物体上的小黑点。
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow('closing',np.hstack([img, closing]))
cv2.waitKey(0)

在这里插入图片描述

24.9.5 形态学梯度(Gradient)

形态学梯度是膨胀图与腐蚀图之差, 对二值图可以将团块(blob)边缘凸显出来, 可以用其来保留边缘轮廓。

gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,k1)

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread(r'D:/datas2/j2.png', 0)
kernel = np.ones((3, 3), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)  # 腐蚀
dilation = cv2.dilate(img, kernel, iterations=1)  # 膨胀
# 开运算:
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

# 形态学梯度
# 其实就是一幅图像膨胀与腐蚀的差别。
# 结果看上去就像前景物体的轮廓。
kernel1 = np.ones((3, 3), np.uint8)
#kernel2=np.uint8(np.asarray([[0,1,0],[1,-8,1],[0,1,0]]))
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel1)
mygrad = dilation - erosion
cv2.imshow('gradient',np.hstack([img, gradient, mygrad]))
cv2.waitKey(0)

在这里插入图片描述

24.9.6 顶帽(Top Hat)

顶帽运算也被称为”礼帽”, 是开运算结果和原图像做差的结果, 可以用来分离比邻近点亮一些的斑块。

tophat = cv2.morphologyEx(img,cv2.MORPH_TOPHAT,k1)

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread(r'D:/datas2/j2.png', 0)
kernel = np.ones((3, 3), np.uint8)
# 礼帽
# 原始图像与  开运算之后得到的图像的差。
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('tophat', tophat)
cv2.waitKey(0)

在这里插入图片描述

24.9.7 黒帽(Black Hat)

黑帽运算是原图像和开运算做差的结果, 可以用来分离比邻近点暗一些的斑块。

blackhat = cv2.morphologyEx(img,cv2.MORPH_BLACKHIAT,k1)

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread(r'D:/datas2/j2.png', 0)
kernel = np.ones((3, 3), np.uint8)
# 黑帽  进行闭运算之后得到的图像与原始图像的差
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat', blackhat)
cv2.waitKey(0)

在这里插入图片描述

24.10 图像边缘检测

边缘检测概述
边缘检测可以提取图像重要轮廓信息, 减少图像内容, 可以用于分割图像、做特征提取等

  1. 边缘检测的一般步骤:
    滤波----(滤出噪声対检测边缘的影响)
    增强----(可以将像素邻域强度变化凸显出来—梯度算子)
    检测----(阈值方法确定边缘)
  2. 常用边缘检测算子:
    Canny算子
    Sobel算子
    Scharr算子
    Laplacian算子
    Roberts 算子、Prewitt算子… …

边缘检测:

  1. 确定边缘——灰度值函数变化尖锐的地方:如sobel算子近似获取梯度(x方向,y方向)
  2. sobel:比较简单,但是如果出现锯齿形边缘,则提取不到连续的边缘
  3. canny:常用/通用,如果出现锯齿形的边缘,也能提取到连续的边缘,但是阈值需要进行调试
  4. 拉普拉斯
  5. 形态学梯度:保留边缘轮廓
  6. 局部阈值处理:如果轮廓有毛边,阈值方法可以优先考虑cv2.ADAPTIVE_THRESH_GAUSSIAN_C,然后调试阈值C

24.10.1 Canny边缘检测

Canny边缘检测算子是John F.Canny于1986 年开发出来的一个多级边缘检测算法, Canny边缘检测算法以Canny的名字命名, 被很多人推崇为当今最优的边缘检测的算法。

Canny边缘检测步骤:

  1. 消除噪声: 一般情况使用高斯平滑滤波器卷积降噪

  2. 计算梯度幅值和方向: 安装Sobel滤波器的步骤操作
    在这里插入图片描述

  3. 非极大值抑制: 排除非边缘像素

  4. 滞后阈值: 滞后阈值需要两个阈值(高阈值和低阈值):
    Ⅰ.如果某一像素位置的幅值超过高阈值,该像素被保留为边缘像素。
    Ⅱ.如果某一像素位置的幅值小于低 阈值,该像素被排除。
    Ⅲ如果某一像素位置的幅值在两个阈值之间该像素仅仅在连接到一个高于高阈值的像素时被保留。

cv2.Canny(image,threshold1,threshold2l,edges[,apertureSize[,L2gradient]]]) → edges
src: 输入原图像(一般为单通道8位图像)
dst: 输出边缘图像要求和src一样的尺寸和类型(单通道)
threshold1: 滞后阈值低阈值(用于边缘连接)
threshold2: 滞后阈值高阈值(控制边缘初始段)推荐高低阈值比值在2:1到3:1之间
apertureSize: 表示Sobel算子孔径大小, 默认值3
L2gradient: 计算图像梯度幅值的标识

在这里插入图片描述

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread(r'D:\datas2\sudoku.jpg')

cv2.imshow('src',img)
cv2.waitKey(0)
edges = cv2.Canny(img, 230, 240)
edges1 = cv2.Canny(img, 130, 240)
edges2 = cv2.Canny(img, 130, 254)
cv2.imshow('Edges',np.hstack([edges1,edges2,edges]))
cv2.waitKey(0)

在这里插入图片描述

24.10.2 Sobel算子

Sobel算子是一个主要用于边缘检测的离散微分算子, 它结合了高斯平滑和微分求导, 用来计算图像灰度函数的近似梯度。

Sobel算子计算过程:

  1. 分别在x和y两个方向求导:
    Ⅰ 水平变化:将Ⅰ与—个奇数大小的内核Gx进行卷积。比如,当内核大小为3时,Gx的计算结果为;
    G x = [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] ⊗ I {{G}_{x}}=\left[ \begin{matrix} -1 & 0 & +1 \\ -2 & 0 & +2 \\ -1 & 0 & +1 \\ \end{matrix} \right]\otimes I Gx=121000+1+2+1I
    Ⅱ.垂直变化∶将Ⅰ 与一个奇数大小的内核Gy进行卷积。比如,当内核大小为3时,Gy的计算结果为:
    G y = [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] ⊗ I {{G}_{y}}=\left[ \begin{matrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ +1 & +2 & +1 \\ \end{matrix} \right]\otimes I Gy=10+120+210+1I
  2. 在图像的每一点,结合以上两个结果求出近似梯度:
    G = G x 2 + G y 2 G=\sqrt{{{G}_{x}}^{2}+{{G}_{y}}^{2}} G=Gx2+Gy2
    另外有时,也可用下面更简单的公式代替:
    G = ∣ G x ∣ + ∣ G y ∣ G=\left| {{G}_{x}} \right|+\left| {{G}_{y}} \right| G=Gx+Gy

Sobel边缘检测函数—cv2.Sobel()

cv2.Sobel(src,ddepth,dx,dy[,dst[,ksize[,scale[, delta[, borderType]]]]]) → dst
src: 输入原图像
dst: 输出图像要求和src一样的尺寸和类型
ddepth: 输出图像的深度, 支持如下组合:

若src.depth0=CV_8U,取ddepth = -1/CV16S/CV_32F/CV64F
若src.depth0=CV_16U/CV_16S,取ddepth = -1/CV 32F/CV_64F
若src.depth0=CV_32F,取ddepth = -1/CV32F/CV64F
若src.depth0= CV 64F,取ddepth = -1/CV 64F

dx: X方向上的差分阶数
dy: Y方向上的差分阶数
ksize: 默认值3, 表示Sobel核大小, 1,3,5,7
scale: 计算导数值时的缩放因子, 默认值1, 表示不缩放
delta(δ): 表示在结果存入目标图之前可选的delta值, 默认值0
borderType: 边界模式, 一般采用默认


import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread(r'd:\datas2\sudoku.jpg', 0)
# cv2.CV_64F 出图像的深度 数据类型 可以使用 -1, 与原图像保持一致 np.uint8
laplacian = cv2.Laplacian(img, cv2.CV_64F)
# 参数 1,0 为只在 x 方向求一 导数 最大可以求 2 导数。
sobelx = cv2.Sobel(img, -1, 1, 0, ksize=3)# ksize 1 3 5 7等奇数
sobelxabs = cv2.convertScaleAbs(sobelx)
# 参数 0,1 为只在 y 方向求一 导数 最大可以求 2 导数。
sobely = cv2.Sobel(img, -1, 0, 1, ksize=3) 

sobelyabs = cv2.convertScaleAbs(sobely)
sobel = cv2.addWeighted(sobelxabs, 0.5, sobelyabs, 0.5, 100)

sobel11 = cv2.Sobel(img, -1, 1, 1, ksize=3)


#cv2.imshow('sobely', sobely)
#cv2.waitKey(0)
plt.subplot(2, 3, 1), plt.imshow(img, cmap='gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])

plt.subplot(2, 3, 2), plt.imshow(sobel11, cmap='gray')
plt.title('sobel11'), plt.xticks([]), plt.yticks([])

plt.subplot(2, 3, 3), plt.imshow(sobelx, cmap='gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])

plt.subplot(2, 3, 4), plt.imshow(sobely, cmap='gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])

plt.subplot(2, 3, 5), plt.imshow(sobel, cmap='gray')
plt.title('Sobel'), plt.xticks([]), plt.yticks([])
plt.show()

在这里插入图片描述

24.10.3 Laplacian算子

Laplacian算子是n维欧几里德中的一个二阶微分算子。

Laplacian算子的定义: L a p l a c e ( f ) = ∂ 2 f ∂ x 2 + ∂ 2 f ∂ y 2 Laplace(f)=\frac{{{\partial }^{2}}f}{\partial {{x}^{2}}}+\frac{{{\partial }^{2}}f}{\partial {{y}^{2}}} Laplace(f)=x22f+y22f

Laplacian边缘检测函数——cv2.Laplacian()

cv2.Laplacian(src, ddepth[,dst[, ksize[, scalel[, deltal[, borderTyel]]]]) → dst
src: 输入原图像(单通道8位图像)
dst: 输出边缘图像要求和src一样的尺寸和通道数
ddepth: 目标图像的深度
Ksize: 用于计算二阶导数的滤波器孔径大小, 须为正奇数, 默认值1
scale: 可选比例因子, 默认值1
delta: 可选参数δ, 默认值0
borderType: 边界模式, 一般采用默认值

24.11 霍夫变换及应用

(待补充)

24.12 直方图计算及绘制

24.12.1 图像直方图概述

直方图是对数据进行统计的一种方法, 可以直观表现图像某属性的数值(频率)分布情况, 包括灰度直方图、RGB直方图等
相关概念及函数:
dims: 需要统计得特征的数目, 如只统计灰度值——dims=1, 统计RGB值——dims=3
bins: 每个特征空间子区域段的数目,也可称为组距(简单理解为直方图分成几个柱子组成)
range: 每个特征空间的取值范围, 如灰度值取值范围[0, 255]
计算直方图函数: cv2.calcHist()

cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]]) ->hist
images: 源图像, 输入数组(或数组集), 需要有相同的深度和尺寸
nimages: 输入数组的个数, 即第一个参数中存放图像个数
channels: 需要统计通道的索引, 表示要使用哪个通道或多个通道(属性)
mask: 可选的操作掩码, 如果不为空, 则必须为8位, 并且与图像有一样大小尺寸
hist: 输出的目标直方图
dims: 需要计算的直方图维度, 必须是正数
histSize: 存放每个维度的直方图尺寸的数组, 即bins
ranges: 表示每一维数值的取值范围
uniform: 直方图是否均匀的标识符, 默认值true
accumulate: 累计标识符, 默认值false, 若为true, 直方图在配置阶段不会被清零

灰度直方图

在这里插入图片描述

import cv2
import numpy as np
from matplotlib import pyplot as plt 
print(cv2.__version__)
img = cv2.imread('D:/datas2/messi5.jpg',0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
plt.hist(img.ravel(),256,[0,256])
plt.show()

在这里插入图片描述

RGB三色直方图

import cv2
from cv2 import waitKey
import numpy as np
from matplotlib import pyplot as plt 

img = cv2.imread('D:/datas2/messi5.jpg')
color = ('b','g','r')
for i,col in enumerate (color):
    histr= cv2.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()

在这里插入图片描述

import cv2
from cv2 import waitKey
import numpy as np
from matplotlib import pyplot as plt 

img = cv2.imread('D:/datas2/messi5.jpg')
mask = np.zeros(img.shape[:2],np.uint8)
mask[130:327,158:473] = 255
color =('b','g','r')
for i,col in enumerate (color):
    histr= cv2.calcHist([img],[i],mask,[256],[0,256])
    plt.plot(histr,color = col)
plt.xlim([0,256])
plt.show()

在这里插入图片描述

24.12.2 直方图相关及应用

  1. 直方图均衡化——cv2.equalizeHist()
    · 直方图均衡化是灰度变换的一个重要应用, 它是通过拉伸像素强度分布范围来增强图像对比度的一种方法, 广泛应用于图像增强处理中。
    · 直方图均衡化(Histogram Equalization)是直方图最典型的应用,是图像点运算的一种。对于一幅输入图像,通过运算产生一幅输出图像,点运算是指输出图像的每个像素点的灰度值由输入像素点决定,即∶ B ( x , y ) = f [ A ( x , y ) ] B(x,y)=f[A(x,y)] B(x,y)=f[A(x,y)]
    · 直方图均衡化是通过灰度变换将一幅图像转换为另—幅具有均衡直方图,即在每个灰度级上都具有相同的像素点数过程。从分布图上的理解就是希望原始图像中 y 轴的值在新的分布中尽可能的展开。变换过程是利用累积分布函数对原始分布进行映射,生成新的均匀拉伸的分布。因此对应每个点的操作是寻找原始分布中 y 值在均匀分布中的位置,如下图是理想的单纯高斯分布映射的示意图∶
    在这里插入图片描述

cv2.equalizeHist(src [,dst]) → dst
src: 输入原图像, Mat类对象即可, 需为8位单通道图像
dst: 均衡化后结果图像, 需和原图一样的尺寸和类型

import cv2
from cv2 import waitKey
import numpy as np
from matplotlib import pyplot as plt 

img = cv2.imread('D:/datas2/messi5.jpg',0)
equ = cv2.equalizeHist(img)
res = np.hstack([img,equ])  # 将2张图片合并一起
cv2.imshow('res',res)
cv2.imshow('result',equ)
cv2.imwrite('1.bmp',equ)
cv2.waitKey (0)
cv2.destroyAllWindows ()

在这里插入图片描述
在这里插入图片描述

CLAHE自适应直方图均衡化
当直方图并不集中在某一区域时,使用直方图均衡化会丢失信息,效果不好

为了解决这个问题,我们需要使用自适应的直方图均衡化。这种情况下,整幅图像会被分成很多小块,这些小块被称为“tiles”(在 OpenCV中tiles 的大小默认是8x8),然后再对每一个小块分别进行直方图均衡化(跟前面类似)。所以在每一个的区域中,直方图会集中在某一个小的区域中(除非有噪声干扰)。如果有噪声的话,噪声会被放大。为了避免这种情况的出现要使用对比度限制。对于每个小块来说,如果直方图中的 bin 超过对比度的上限的话,就把其中的像素点均匀分散到其他bins中,然后在进行直方图均衡化。最后,为了去除每一个小块之间“人造的”(由于算法造成)边界,再使用双线性差值,对小块进行缝合。

  1. 直方图对比——cv2.compareHist()
    直方图对比就是根据一定的标准来比较两幅图像的直方图的相似度, 近而确定图像的相似度, opencv提供对比直方图相似度的函数为: cv2.compareHist()

cv2.compareHist(H1,H2,method retval →retrval
H1: 需要比较的直方图1
H2: 需要比较的直方图2
method: 直方图对比的方法, 有如下四种:

cv2.HISTCMP_CORREL, ——相关性方法(值越大匹配度越高)

d c o r r e l ( H 1 , H 2 ) = ∑ i H 1 ( i ) ⋅ H 2 ( i ) ∑ i H 1 ( i ) ⋅ H 2 ( i ) {{d}_{correl}}({{H}_{1}},{{H}_{2}})=\frac{\sum\limits_{i}{{{H}_{1}}(i)\cdot {{H}_{2}}(i)}}{\sqrt{\sum\limits_{i}{{{H}_{1}}(i)\cdot {{H}_{2}}(i)}}} dcorrel(H1,H2)=iH1(i)H2(i) iH1(i)H2(i)

cv2.HISTCMP_CHISQR, ——卡方测量法(值越小匹配度越高)
d c h i − s q u a r e ( H 1 , H 2 ) = ∑ i ( H 1 ( i ) − H 2 ( i ) ) 2 H 1 ( i ) + H 2 ( i ) {{d}_{chi-square}}({{H}_{1}},{{H}_{2}})=\sum\limits_{i}{\frac{{{({{H}_{1}}(i)-{{H}_{2}}(i))}^{2}}}{{{H}_{1}}(i)+{{H}_{2}}(i)}} dchisquare(H1,H2)=iH1(i)+H2(i)(H1(i)H2(i))2
cv2.HISTCMP_INTERSECT, ——直方图相交法(值越大匹配度越高)
d i n t e r s c e t i o n ( H 1 , H 2 ) = ∑ i min ⁡ ( H 1 ( i ) , H 2 ( i ) ) {{d}_{interscetion}}({{H}_{1}},{{H}_{2}})=\sum\limits_{i}{\min ({{H}_{1}}(i),{{H}_{2}}(i))} dinterscetion(H1,H2)=imin(H1(i),H2(i))
cv2.HISTCMP_BHATTACHARYYA ,——Bhattacharyya测量法(小)
d B h a t t a c h a r y y a ( H 1 , H 2 ) = 1 − ∑ i H 1 ( i ) ⋅ H 2 ( i ) ∑ i H 1 ( i ) ⋅ ∑ i H 2 ( i ) {{d}_{Bhattacharyya}}({{H}_{1}},{{H}_{2}})=\sqrt{1-\sum\limits_{i}{\frac{\sqrt{{{H}_{1}}(i)\cdot {{H}_{2}}(i)}}{\sqrt{\sum\limits_{i}{{{H}_{1}}(i)}\cdot \sum\limits_{i}{{{H}_{2}}(i)}}}}} dBhattacharyya(H1,H2)=1iiH1(i)iH2(i) H1(i)H2(i)

  1. 反向投影——cv2.calcBackProject()
    · 反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式, 简单来说, 所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在该特征的方法。例如, 你有一个肤色直方图 ( Hue-Saturation 直方图 ),你可以用它来寻找图像中的肤色区域, 具体原理:
    · 假设已经通过下图得到一个肤色直方图(Hue-Saturation),直方图就是模型直方图 ( 代表手掌的皮肤色调).可以通过掩码操作来抓取手掌所在区域的直方图
    · 我们要做的就是使用模型直方图 (代表手掌的皮肤色调) 来检测测试图像中的皮肤区域。以下是检测的步骤:
    ①对测试图像中的每个像素 p(I,j),获取色调数据并找到该色调 (hij, sij )在直方图中的bin的位置。
    ②查询 模型直方图中对应的bin——并读取该bin的数值。
    ③将此数值储存在新的图像中 (BackProjection)。也可以先归一化模型直方图 ,这样测试图像的输出就可以在屏幕显示了。
    ④通过对测试图像中的每个像素采用以上步骤,得到了 BackProjection 结果图
    ⑤使用统计学的语言, BackProjection 中储存的数值代表了测试图像中该像素属于皮肤区域的概率 。亮起的区域是皮肤区域的概率更大(事实确实如此),而更暗的区域则表示更低的概率(注意手掌内部和边缘的阴影影响了检测的精度)。
    反向投影的作用:
    反向投影应用在输入图像(大)中查找与特定图像(模板图像), 最匹配的点或者区域, 也就是定位模板图像出现在输入图像的位置

cv2.calcBackProject(images, chamnels, hist,ranges,scale[,dst]) → dst
images: 输入数组或数组集, 需要为相同深度和尺寸
nimages: 输入数组个数, 也就是图像数量
channels: 需要统计的通道索引
hist: 输入的直方图
backProject: 目标反向投影阵列, 需为单通道, 并且和image[0]有相同大小和深度
ranges: 表示每一维度数组的每一维的边界阵列(取值范围)
uniform: 直方图是否均匀标识, 默认值true

24.13 模板匹配及应用

24.13.1 模板匹配

模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。模板匹配不是基于直方图的, 而是通过在输入图像上滑动图像块(模板)同时比对相似度, 来对模板和输入图像进行匹配的一种方法。

应用:
①目标查找定位
②运动物体跟踪
③其他…

24.13.2 模板匹配——cv2.matchTemplate()

cv2.matchTemplate(image,templ,method[,result]) → result
image: 待搜索图像(大图)
templ: 搜索模板, 需和原图一样的数据类型且尺寸不能大于源图像
result: 比较结果的映射图像, 其必须为单通道, 32位浮点型图像, 如果原图(待搜索图像)尺寸为W x H, 而templ尺寸为 w x h, 则result尺寸一定是(W-w+1)x(H-h+1)
method: 指定的匹配方法, 有如下6种:

平方差匹配cv2.TM_SQDIFF ——平方差匹配法(最好匹配0)
这类方法利用平方差来进行匹配,最好匹配为0,匹配越差,匹配值越大:
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) − I ( x + x ′ , y + y ′ ) ) 2 R(x,y)={{\sum\limits_{x',y'}{(T(x',y')-I(x+x',y+y'))}}^{2}} R(x,y)=x,y(T(x,y)I(x+x,y+y))2

标准平方差匹配cv2.TM_SQDIFF_NORMED——归一化平方差匹配法(最好匹配0)
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) − I ( x + x ′ , y + y ′ ) ) 2 ∑ x ′ , y ′ T ( x ′ , y ′ ) 2 ⋅ ∑ x ′ , y ′ I ( x + x ′ , y + y ′ ) 2 R(x,y)=\frac{\sum\limits_{x',y'}{{{(T(x',y')-I(x+x',y+y'))}^{2}}}}{\sqrt{\sum\limits_{x',y'}{T{{(x',y')}^{2}}}\cdot \sum\limits_{x',y'}{I{{(x+x',y+y')}^{2}}}}} R(x,y)=x,yT(x,y)2x,yI(x+x,y+y)2 x,y(T(x,y)I(x+x,y+y))2

相关匹配cv2.TM_CCORR——相关匹配法(最坏匹配0)
这类方法采用模板和图像见的乘法操作,所以较大的数表示匹配程度较高,0表示最坏的匹配效果:
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) ⋅ I ( x + x ′ , y + y ′ ) ) R(x,y)=\sum\limits_{x',y'}{(T(x',y')\cdot I(x+x',y+y'))} R(x,y)=x,y(T(x,y)I(x+x,y+y))

标准相关匹配cv2.TM_CCORR_NORMED——归一化相关匹配法(最坏匹配0)
R ( x , y ) = ∑ x ′ , y ′ ( T ( x ′ , y ′ ) ⋅ I ( x + x ′ , y + y ′ ) ) ∑ x ′ , y ′ T ( x ′ , y ′ ) 2 ⋅ ∑ x ′ , y ′ I ( x + x ′ , y + y ′ ) 2 R(x,y)=\frac{\sum\limits_{x',y'}{(T(x',y')\cdot I(x+x',y+y'))}}{\sqrt{\sum\limits_{x',y'}{T{{(x',y')}^{2}}}\cdot \sum\limits_{x',y'}{I{{(x+x',y+y')}^{2}}}}} R(x,y)=x,yT(x,y)2x,yI(x+x,y+y)2 x,y(T(x,y)I(x+x,y+y))

cv2.TM_CCOEFF——系数匹配法(最好匹配1)
这类方法将模板对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示没有任何相关性(随机)
R ( x , y ) = ∑ x ′ , y ′ ( T ′ ( x ′ , y ′ ) ⋅ I ′ ( x + x ′ , y + y ′ ) ) R(x,y)=\sum\limits_{x',y'}{(T'(x',y')\cdot I'(x+x',y+y'))} R(x,y)=x,y(T(x,y)I(x+x,y+y))
其中: T ′ ( x ′ , y ′ ) = T ( x ′ , y ′ ) − 1 w ⋅ h ∑ x ′ ′ , y ′ ′ T ( x ′ ′ , y ′ ′ ) , I ′ ( x + x ′ , y + y ′ ) = I ( x + x ′ , y + y ′ ) − 1 w ⋅ h ∑ x ′ ′ , y ′ ′ I ( x + x ′ ′ , y + y ′ ′ ) T'(x',y')=T(x',y')-\frac{1}{w\cdot h}\sum\limits_{x'',y''}{T(x'',y'')},I'(x+x',y+y')=I(x+x',y+y')-\frac{1}{w\cdot h}\sum\limits_{x'',y''}{I(x+x'',y+y'')} T(x,y)=T(x,y)wh1x,yT(x,y),I(x+x,y+y)=I(x+x,y+y)wh1x,yI(x+x,y+y)

cv2.TM_CCOEFF_NORMED——化相关系数匹配法(最好匹配1)

R ( x , y ) = ∑ x ′ , y ′ ( T ′ ( x ′ , y ′ ) ⋅ I ′ ( x + x ′ , y + y ′ ) ) ∑ x ′ , y ′ T ′ ( x ′ , y ′ ) 2 ∑ x ′ , y ′ I ′ ( x + x ′ , y + y ′ ) 2 R(x,y)=\frac{\sum\limits_{x',y'}{(T'(x',y')\cdot I'(x+x',y+y'))}}{\sum\limits_{x',y'}{T'{{(x',y')}^{2}}\sum\limits_{x',y'}{I'{{(x+x',y+y')}^{2}}}}} R(x,y)=x,yT(x,y)2x,yI(x+x,y+y)2x,y(T(x,y)I(x+x,y+y))

通常,随着从简单的测量(平方差)到更复杂的测量(相关系数),我们可获得越来越准确的匹配(同时也意味着越来越大的计算代价). 最好的办法是对所有这些设置多做一些测试实验,以便为自己的应用选择同时兼顾速度和精度的最佳方案

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('D:/datas2/messi5.jpg', 0)
src = cv2.imread('D:/datas2/messi5.jpg')

img_ori = img.copy()
ball = cv2.imread('D:/datas2/ball.jpg', 0)
# cv2.imshow('src', img)
# cv2.waitKey(0)
# cv2.imshow('ball', ball)
# cv2.waitKey(0)

img_h, img_w = img.shape
ball_h, ball_w = ball.shape
#选取比较方法
method = cv2.TM_SQDIFF
##进行匹配与计算
res = cv2.matchTemplate(img, ball, method)#res:原图每个区域与模板的相似度的值
#最值的获取,也就是最相似的区域及其位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
top_left = min_loc
bottom_right = (top_left[0] + ball_w, top_left[1] + ball_h)
#绘制矩形 在灰度图上显示
cv2.rectangle(img, top_left, bottom_right, 255, -1)
cv2.imshow('result', np.hstack([img_ori, img]))
cv2.waitKey(0)
#绘制矩形 在彩色图上显示
cv2.rectangle(src, top_left, bottom_right, (255, 255, 0), 5)
cv2.imshow('result', src)
cv2.waitKey(0)

在这里插入图片描述
在这里插入图片描述

24.13.3 矩阵归一化——cv2.normalize()

cv2.normalize(src,dst[,alphal,betal,norm_type[,dtype[, mask]]]]) → dst
src: 输入原图像
dst: 输出结果图像, 需要和原图一样的尺寸和类型
alpha: 归一化后的最小值, 默认值1
beta: 归一化后的最大值, 默认值0
norm_type: 归一化类型, 可选NORM_INF, NORM_L1, NORM_L2(默认)等
dtype: 默认值-1, 此参数为负值时, 输出矩阵和src有同样类型
mask: 可选的掩码操作

对矩阵进行归一化

24.13.4 寻找最值——cv2.minMaxLoc()

cv2.minMaxLoc(src, mask=None) 求这个矩阵的最小值,最大值,并得到最大值,最小值的索引
src: 输入原图像, 单通道图像
minVal: 返回最小值的指针, 若无需返回, 则设置0
maxVal: 返回最大值的指针, 若无需返回, 则设置0
minLoc: 返回最小位置的指针,若无需返回, 则设置0
maxLoc: 返回最大位置的指针,若无需返回, 则设置0
mask: 可选的掩码操作

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LiongLoure

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值