opencv学习笔记

189 阅读34分钟

这个笔记是我跟着B站OpenCV学习课程一步步自己搭建的代码以及以及对其中一些知识的理解,笔记只涵盖前面学习部分,并不包括项目实战。如有理解错误的地方,欢迎指出。

B站视频链接

www.bilibili.com/video/BV12Z…

1.day01

1.1创建和显示窗口

import cv2
​
# 1.创建命名窗口 namedWindow
# 2.显示窗口 imshow
# 3.摧毁窗口 destroyAllWindows
# 4.改变窗口大小 resizeWindows
# 5.等待用户输入 waitKey# 创建窗口
# cv2.namedWindow("firstWindow", cv2.WINDOW_AUTOSIZE )  不允许修改窗口大小 只能是当flag为cv2.WINDOW_NORMAL时才能修改大小
cv2.namedWindow("firstWindow", cv2.WINDOW_NORMAL)  # 标记窗口
# 展示窗口
cv2.imshow("firstWindow", 0)  # 窗口的编号
# 修改窗口大小
cv2.resizeWindow("firstWindow", 600, 800)  # 指定宽高
# 等待按键 -键盘按键
ord('q')  # 计算ascii码的函数
# 0 表示接收任意按键--如果给其他的整数 那么就表示等待按键的时间(单位毫秒)
# cv2.waitKey(0)  # 会返回按键的ascii码的值# 可以使用waitKey根据特定按键进行退出
# key是int型 最少是16位 可是ascii是8位 所以需要将key后面几位取出来跟ascii码进行比较 ---也可以删去 这个无所谓
if cv2.waitKey(0) & 0xFF == ord('q'):
    cv2.destroyAllWindows()
​

1.2显示图像

import cv2
import matplotlib.pyplot as plt
from tools import cv_show
​
# flag表示是以什么形式进行读取 默认是BGR  彩色图片
img = cv2.imread("../resource/IMG_01.jpg")  # uint8表示无符号的 八位数字全用来表示图片 所以最大值是2^8-1 (从0开始算)
# 使用plt进行显示  一般不要用这些方式去展示
# plt.imshow(img)
# plt.show()# 调用自己封装好的函数
cv_show.show_img(img, "IMG", (600, 600))

2.day02

2.1保存图像

import cv2
from tools import cv_show
​
# 保存图片 imwrite
img = cv2.imread("../resource/IMG_01.jpg")
cv2.namedWindow("IMG")
cv_show.show_img(img)
# 保存图片
cv2.imwrite("../resource/IMG_001.jpg", img)  # 自定义保存路径 注意:图片的尾缀也要加上

2.2读取摄像头和视频信息

import cv2
​
# 视频其实就是多张图片组成的
# 1.读取视频 打开视频文件 如果打开失败 其实不会报错
cap = cv2.VideoCapture("../resource/Video1.mp4")  # 如果是视频文件 那么直接指定路径 如果是摄像头那么可以用不同的设备进行指定
# 2.检查是否正确打开 cap.isOpened()
# 3.对视频文件进行读取
while cap.isOpened():  # 返回boolean类型的值
    success, frame = cap.read()  # 返回是否读取到了数据以及每一帧的图片
    cv2.imshow("Video", frame)
    key = cv2.waitKey(10)
# 注意:别忘了释放资源
cap.release()
cv2.destroyAllWindows()
​

2.3录制视频

import cv2
​
# 视频录制 其实就是将摄像头读取到的每一帧图像进行保存 videoWriter
cap = cv2.VideoCapture(0)
# 1.创建保存视频的对象
# fourcc表示视频的格式
# *"mp4v"是解包操作 等同于 'm','p','4','v'
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
vw = cv2.VideoWriter("../resource/output.mp4", fourcc, 20, (640, 480))  # 20表示帧率  (640, 480)表示分辨率# 其他的一些视频格式 avi格式 fourcc = cv2.VideoWriter_fourcc(*"XVID") 其实也很少用到
​
while cap.isOpened():
    success, frame = cap.read()
    vw.write(frame)  # 开始写入
    cv2.imshow("IMG", frame)
    if cv2.waitKey(1000 // 20) == ord('q'):  # cv2.waitKey(1)表示一毫秒就一张图片是1000帧的视频 --- 1000//20表示一秒截取怎么多张图片 其实就是表示帧率 如果我们说24帧的视频 其实就每隔1000/24毫秒截取一张
        break
​
# 释放资源
cap.release()
cv2.destroyAllWindows()
​

2.4控制鼠标

import cv2
import numpy as np
​
# opencv允许我们对窗口上的鼠标动作做出响应
# 1.定义回调函数
# event鼠标事件 x,y 触发事件的坐标 flags组合按键  userdata用户数据   --- 一定要有这三个参数
def mouse_callback(event, x, y, flags, userdata):
    print(event, x, y, flags, userdata)
    # 对于鼠标触发事件对应的输出值对照表在img.png中
    # 根据事件进行判断 控制
    if event == 2:
        cv2.destroyAllWindows()
#
cv2.namedWindow("MOUSE", cv2.WINDOW_NORMAL)
cv2.resizeWindow("MOUSE", 600, 800)
# 2.设置鼠标回调函数
cv2.setMouseCallback("MOUSE", mouse_callback, "123") # 窗口名 用户名
# 全黑图片
img = np.zeros((800, 600, 3), np.uint8)
​
while True:
    cv2.imshow("MOUSE", img)
    key = cv2.waitKey(1)
    if key == ord('q'):
        break
cv2.destroyAllWindows()
​

img.png

2.5trackbar用法

什么是trackbar?其实就相当于是一个可以拉动的进度条 ,可以自己设定值的范围 通过实时获取值来实现实时变换的效果

import cv2
import numpy as np
​
# trackBar控件可以用于控制三原色的值 看起来有点像滚轮调色板
cv2.namedWindow("trackBar", cv2.WINDOW_NORMAL)
cv2.resizeWindow("trackBar", 600, 800)
​
​
# 定义回调函数
def callback(value):
    print(value)
​
​
# 1.创建trackBar  创建三原色滚轮 值是0-255 
# R是trackBar的名字 后续要根据名字进行取值  trackBar是窗口的名字   0, 255是滑动范围 这个可以自己设定
cv2.createTrackbar('R', "trackBar", 0, 255, callback)
cv2.createTrackbar('G', "trackBar", 0, 255, callback)
cv2.createTrackbar('B', "trackBar", 0, 255, callback)
​
img = np.zeros((800, 600, 3), np.uint8)
# 时刻获取到当前trackbar的值
while True:
    # 2.获取trackbar 的值
    r = cv2.getTrackbarPos("R", "trackBar")
    g = cv2.getTrackbarPos("G", "trackBar")
    b = cv2.getTrackbarPos("B", "trackBar")
    # 修改img的值
    img[:] = [b, g, r]  # img[:]会输出三个通道的值的矩阵 然后使用获取到的rgb进行赋值  
    #   注意:cv2读取到的图片通道是BGR顺序的 所以imshow的时候需要是BGR的顺序才会正常显示
    cv2.imshow("trackBar", img)
    key = cv2.waitKey(1)
    if key == ord('q'):
        break
cv2.destroyAllWindows()
​

image-20230712221438292.png

3.day03

3.1HSV、HSL和YUV颜色表示

# 多种色彩空间的讲解
# 1.HSV
# --- Hue色相(即色彩) Saturation饱和度(颜色的纯度--混入多少白色) Value亮度# 为什么需要使用HSV --opencv需要 可直接根据hsv的值判断是否是背景# 2.HSL
# --Hue Saturation(浓度--稀释程度) lightness(混入的白色和黑色的量)# 3.YUV
# Y表示明亮度 U和V表示色度 描述影像色彩饱和度用于指定像素的颜色
# 优点:占用极少带宽

im2g.png

3.2颜色空间的转换

import cv2
​
​
# 色彩通道转化 cvtColordef callback(value):
    pass
​
​
cv2.namedWindow("color", cv2.WINDOW_NORMAL)
cv2.resizeWindow("color", 800, 600)
img = cv2.imread("../resource/IMG_01.jpg")
​
# 定义颜色空间转化列表 COLOR_BGR2BGRA等于加上了一个透明度
colorspace = [cv2.COLOR_BGR2RGB, cv2.COLOR_BGR2BGRA, cv2.COLOR_BGR2GRAY, cv2.COLOR_BGR2HSV, cv2.COLOR_BGR2YUV]
​
# 设置trackbar
cv2.createTrackbar("Space", "color", 0, 4, callback) # 将设定的值范围设定为0-4作为列表索引
while True:
    i = cv2.getTrackbarPos("Space", "color")
    # 根据trackbar获取到的值进行索引切片
    cvt_img = cv2.cvtColor(img, colorspace[i])
    cv2.imshow("color", cvt_img)
    key = cv2.waitKey(10)  # 等待10ms后继续执行新的一次循环 如果是0的话 那么就会等待你输入完后才显示你修改后的图片
    if key == ord('q'):
        break
cv2.destroyAllWindows()

image-20230712221653481.png

#### 3.3mat深浅拷贝
import cv2
import numpy as np
​
# mat是opencv用来表示图像的一种数据结构 其实就是我们要展示的图片
# mat由header和data组成用来记录图片信息
# ndarray四种常见属性  data size dtype shape# python中图片数据为ndarray 所以对于mat进行深浅拷贝其实就是对ndarray进行深浅拷贝img = cv2.imread("../resource/IMG_01.jpg")
​
# 浅拷贝 -- 跟原本的图片的数据底层是一样的 
img2 = img.view()
# 深拷贝 --额外开一份
img3 = img.copy()
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
cv2.resizeWindow("img", 1000, 1000)
img[10:100, 10:100] = [0, 0, 255]  # 对原图进行修改# ***使用np.hstack对数组进行合并 横向合并 --只能是相同shape的拼在一起
cv2.imshow("img", np.hstack((img, img2, img3))) 
# 可以看到前面两张是一样的 说明浅拷贝是基于原图的 深拷贝是单开的
​
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230604212750909.png

3.4颜色通道的分离与合并

import cv2
import numpy as np
​
img = np.zeros((280, 640, 3))
# 1.分割图像
b, g, r = cv2.split(img)
# 修改一些颜色
b[10:100, 10:100] = 255
g[10:100, 10:100] = 255
​
# 2.合并通道
img_mer = cv2.merge((b, g, r)) # 输入一个元组  
img_RGB = cv2.merge((r,g,b)) # 也可以使用这种方式来完成色彩空间的转换
cv2.imshow("img", np.hstack((img, img_mer)))
cv2.waitKey(0)

image-20230604212937671.png

3.5画直线

import cv2
import numpy as np
​
# 画直线
img = np.zeros((500, 500, 3))
# 坐标表示的是左上角和右下角  
cv2.line(img, (100, 100), (200, 200), (0, 255, 255), 2, cv2.LINE_AA)
cv2.imshow("line", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

4.day04

4.1绘制矩形和圆

import cv2
import numpy as np
​
img = np.zeros((680, 680, 3), np.uint8)
# 1.绘制矩形
# 参数与线段是一样的
cv2.rectangle(img, (10, 10), (100, 100), (0, 255, 255), 5)
cv2.imshow("rect", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
​
# 2.绘制圆 中心点坐标 半径
cv2.circle(img, (200, 200), 50, (0, 255, 0), 4, 32)  # ** 注意:当我们给linetype设置的数字越大 那么就越少毛刺 但是数字必须是2的倍数
cv2.imshow("circle", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

4.2绘制椭圆

import numpy as np
import cv2
​
# 绘制椭圆
img = np.zeros((480, 640, 3), np.uint8)
​
# 参数理解   
cv2.ellipse(img, 
            (320, 240), # 中心点坐标
            (100, 50), # 长度和宽度的一半 
            45, # 角度--倾斜角度(顺时针旋转) 原本是躺下的
            0, 360, # 从哪个角度开始 从哪个角度结束---画几分之几的椭圆
            (0, 255, 0), # 颜色
            3, # 线条宽度
            32) # 线条风格 
cv2.imshow("ellipse",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230604211046509.png

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

import numpy as np
import cv2
​
img = np.zeros((480, 640, 3), np.uint8)
# 1.绘制多边形
pts = np.array([(200, 10), (150, 100), (250, 100)], np.int32)
# 参数理解 img 各个点的坐标--必须是int32位及以上 是否封闭图形
cv2.polylines(img, [pts], True, (0, 255, 0), 2, 32)  # 注意:要把pts框起来 里面代表的是点集的集合 可以放多个点集在里面
cv2.imshow("poly", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
​
# 2.填充多边形 参数和polylines一样
cv2.fillPoly(img, [pts], (0, 255, 255), 32)
cv2.imshow("fill", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230712222429431.png

4.4绘制文本以及中文显示

import numpy as np
import cv2
from PIL import ImageFont, ImageDraw, Image
​
img = np.zeros((480, 640, 3), np.uint8)
# 1.绘制文本 ---会中文乱码
# 参数理解 
cv2.putText(img, # 图片
            "hell0", # 文本
            (150, 150), # 文字左下角坐标
            cv2.FONT_HERSHEY_SIMPLEX, # 字体类型
            3, # 大小
            [0, 255, 255])
​
cv2.imshow("text", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
​
# 2.绘制中文 --opencv无法直接绘制中文-- 使用pillow包即可
img = np.full((200, 200, 3), fill_value=255, dtype=np.uint8)
​
# 导入字体文件
font = ImageFont.truetype("./msyhbd.ttc", 15)
# 创建一个pillow
img_pil = Image.fromarray(img)
draw = ImageDraw.Draw(img_pil)
draw.text((10, 150), "你好", font=font, fill=(0, 255, 0))
​
# 然后再重新变回ndarray
img = np.array(img_pil)
cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

5.day05

5.1图像运算

图片的运算 其实就是矩阵的运算

import cv2
​
# 读取图片
img1 = cv2.imread("../resource/IMG_01.jpg")
img2 = cv2.imread("../resource/IMG_02.png")
​
img1 = cv2.resize(img1, (640, 640))
img2 = cv2.resize(img2, (640, 640))
​
# 1.加法 cv2.add  用于两张图片进行叠加的功能
# 在进行加法之前 需要size一致 长、宽、维度  也可以使用数组切片的方式进行 size统一
# 如果数字相加超过255的话 那么就全部变成255 --截取操作
add_img = cv2.add(img1, img2)
​
cv2.imshow("add_img", add_img)
cv2.waitKey(0)
​
# 2.跟单个数字进行运算 其实就是每个数字都加上
# ** 超出255的数字会被截断 相当于 %256  (88+200)%256 = 32
print(img1[600, 200, -1]) # 88
img1 += 200
print(img1[600, 200, -1])# 32
​
cv2.imshow("IMG+200", img1)
cv2.waitKey(0)
​
# 3.减法运算 cv2.subtract  原理其实就是跟加法是一样的# 4.乘法 cv2.multiply# 5.除法 cv2.divide

image-20230712224028141.png

5.2图像融合

import cv2
​
# 图像黑色是0 白色是255# 图片融合 不是简单的加法 而是做了一个线性运算 每个图片都有一个权重用来实现不同的效果
# w1*img1 + w2*img2 +....+bias     bias是偏差 --可以用来控制融合图片的整体亮度
# shape要相同
# 权重没有要求 不是说加起来就一定要是1(最好是用小数进行表示)  其实加法就是权重全是1
aW_img = cv2.addWeighted(img1, 0.8, img2, 0.7, -10)
cv2.imshow("aw_img", aW_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

5.3opencv位运算

运算理解

设定两个数 a = 12(转换为二进制1100) b = 9(转换为二进制1001)

与运算: 对应位数全为1 该位数字才为1 否则为0 a&b=8(二进制1000)

非运算:其实就是0变1 1变0 公式:~x=-(x+1)

~b = 1111 1111 1111 1111 1111 1111 1111 0110(int是32位 )这个是补码 要转换为原码需要进行转换

(原码变补码:取反加一 补码变原码:减一取反(符号位不取反))第一个就是符号位

减一:1111 1111 1111 1111 1111 1111 1111 0101

取反:1000 0000 0000 0000 0000 0000 0000 1010 所以~b = -10

注意:在opencv中是围绕着255进行展开的 ~0 = 255 公式:待非的值 + 非后的值 = 255

或运算:两个数对应位数进行比较 只要有一个是1那么结果位数就为1 a|b = 13(二进制1101)

异或运算: 两个数相同则为0 不同则为1 a^b = 5 (二进制101)

import cv2
import numpy as np
​
# 什么是位运算 其实就是逻辑运算 --与 或 非 异或(其实就是有一个是false就是false)
# 搞懂 与 非 或 非或运算!!!
​
img1 = cv2.imread("../resource/IMG_01.jpg")
img2 = cv2.imread("../resource/IMG_02.png")
img1 = cv2.resize(img1, (640, 640))
img2 = cv2.resize(img2, (640, 640))
​
# 与运算  先把下面的数字转换为二进制 然后再求与运算(两位同时为1 那么结果才为1 否则为0) 最后在转回十进制进行表示
print(204 & 213)
# 非运算  注意:opencv中的非运算 255 反过来是 0  与自身进行操作
print(~255)
# 异或运算 两个数字对应位值不同 那么结果为1 否则为0
print(204 ^ 213)
# 或运算 只要有一个是1 那么就是1
print(204 | 213)
​
# 对于opencv
​
# 1.非操作
img1_not = cv2.bitwise_not(img1)
cv2.imshow("img1_not", np.hstack((img1, img1_not)))
cv2.waitKey(0)
cv2.destroyAllWindows()
# 查看矩阵运算结果  0变成了255  其实就是 -- 待非的值 + 非后的值 = 255
print(img1[:3, :3])
print("------------")
print(img1_not[:3, :3])
​
# 2.与操作
img1_and = cv2.bitwise_and(img1, img2)
cv2.imshow("img_and", np.hstack((img1, img2, img1_and)))
cv2.waitKey(0)
cv2.destroyAllWindows()
print("与操作")
print(img1[100:103, 100:103])
print("------------")
print(img2[100:103, 100:103])
print("--------------")
print(img1_not[100:103, 100:103])
​
# 3.或操作 对应元素进行或运算
img_or = cv2.bitwise_or(img1, img2)
cv2.imshow("img_or", np.hstack((img1, img2, img_or)))
cv2.waitKey(0)
cv2.destroyAllWindows()
print("或操作")
print(img1[100:103, 100:103])
print("------------")
print(img2[100:103, 100:103])
print("--------------")
print(img_or[100:103, 100:103])
​
# 4.异或操作
img_xor = cv2.bitwise_xor(img1, img2)
cv2.imshow("img_xor", np.hstack((img1, img2, img_xor)))
cv2.waitKey(0)
cv2.destroyAllWindows()
print("异或操作")
print(img1[100:103, 100:103])
print("------------")
print(img2[100:103, 100:103])
print("--------------")
print(img_xor[100:103, 100:103])
print(11 ^ 3)  # 8

5.4resize用法

# resize用法 就是图片的放大与缩小img1 = cv2.imread("../resource/IMG_01.jpg")
img2 = cv2.imread("../resource/IMG_02.png")
print(img1.shape)  # (1080, 1920, 3) 表示的是1080行 1920列  其实就是高度和宽度
print(img2.shape)
​
# 1.按照固定shape进行缩放
# ** dsize中表示的又是宽度和高度 跟读取图片shape是反过来的
img1_resize = cv2.resize(img1, dsize=(640, 640), interpolation=cv2.INTER_AREA)
​
# 按照比例进行缩放 按照x轴和y轴的比例
img2_resize = cv2.resize(img2, dsize=None, fx=0.5, fy=0.5)
print(img2_resize.shape)

img3.png

6.day06

6.1图像翻转与旋转

图像翻转

图像翻转相当于图像沿着图像外的一根不同平面的轴在转动,上下左右翻转就代表的是轴的放置状态是水平还是垂直的

# 1.图像的翻转  flip
flip_img = cv2.flip(img1, 0)  # 0表示上下翻转  >0表示左右翻转 <0表示上下左右翻转

image-20230712224846723.png

图像旋转

图像旋转相当于绕着图像中心点进行转动,需要注意的是:旋转之后长和宽可能会发生改变

# 2.图像旋转  rotate
rotate_img = cv2.rotate(img1, rotateCode=cv2.ROTATE_90_CLOCKWISE)  # 沿着顺时针方向旋转
# cv2.ROTATE_90_COUNTERCLOCKWISE 表示逆时针旋转90度

image-20230712224740990.png

6.2仿射变换之图像平移

什么是仿射变换:什么是仿射变换? 其实就是前面一系列图像变换(翻转、平移)的总称 具体就是通过一个矩阵与原图坐标进行计算 得到新的坐标 完成变换

如果我们想向上/下移动图片 那么我们就只需要设定一个变换矩阵M为如下所示(将ty设置为我们需要的值即可--) 左右移动也同理

注意:是对每个像素点进行下面的操作

image-20230604220043933.png

# 什么是仿射变换? 其实就是前面一系列图像变换的总称 具体就是通过一个矩阵与原图坐标进行计算 得到新的坐标 完成变换# ** 注意:变换矩阵M必须是float32的
M = np.float32([[1, 0, 100],
                [0, 1, 100]])
​
# 注意:是对每个像素点进行下面的操作
new = cv2.warpAffine(img1, M, (w, h))  # 是先宽度后高度

image-20230604222220736.png

6.3仿射变换之获取变换矩阵M

为什么要通过API获取变换矩阵? 如果我们只是进行一些平移的操作的话,可以自己设置变换矩阵,但是如果我们进行一些更加复杂的仿射变换(例如,旋转结合缩放)那么自己设置变换矩阵就不太可能了。

注意:通过设置变换矩阵和仿射变换可以实现图像的任意角度旋转。

image-20230604225551837.png

# **为什么要通过API获取变换矩阵?**如果我们只是进行一些平移的操作的话,
# 可以自己设置变换矩阵,但是如果我们进行一些更加复杂的仿射变换(例如,旋转)那么自己设置变换矩阵就不太可能了。所以需要API来获取变换矩阵
h, w, ch = img1.shape
M = cv2.getRotationMatrix2D((240, 240),
                            30,  # 旋转角度 ** 逆时针旋转
                            1)  # 缩放比例
new = cv2.warpAffine(img1, M, (w, h))
​

image-20230604230137124.png

获取变换矩阵的另外一种方法

另外一种确定M的方法:通过三个点和变换后的三个点进行解方程从而获取到变换矩阵M

# ****
# 作用:另外一种确定M的方法:通过三个点和变换后的三个点进行解方程
# **** 需要原始图片三个点坐标和对应变换后的坐标
# 旋转前 注意:类型必须是float32的
src = np.float32([[200, 100], [300, 100], [200, 300]])
# 旋转后
dst = np.float32([[100, 150], [50, 300], [460, 200]])
# 获取到仿射矩阵
M = cv2.getAffineTransform(src=src, dst=dst)
​
new = cv2.warpAffine(img1, M, (w, h))
​

6.4仿射变换之透视变换

什么是透视变换?其实就是将一种坐标系转换为另外一种坐标系 简单来说就是将一张斜的图像变正 视角变正

​
​
# 什么是透视变换?其实就是将一种坐标系转换为另外一种坐标系 简单来说就是将一张斜的图像变正
# 获取透视变换矩阵 需要四个点(是图片的四个角)需要变正的范围
# 原图的  四个坐标 
src = np.float32([[], [], [], [], []])
# 变换后的坐标当作变换后的原点
dst = np.float32([[], [], [], []])  
# 获取变换矩阵
M = cv2.getPerspectiveTransform(src, dst)
# 进行透视变换
new = cv2.warpPerspective(img1, M, (600, 600))  # 变换后的图片大小 如果dst是原点 那么就要根据dst来确定dsize

image-20230606213222570.png

7.day07

7.1卷积操作

什么是波?一般是指图片中的一些噪声

什么是滤波器?其实就是一些卷积核,不同的卷积核进行卷积操作(也叫滤波)会有不同的过滤效果,可以将图片的特征过滤出来也可以用于降噪

image-20230606221840960.png

image-20230606222234924.png

为什么卷积核的大小一般都是奇数?

原因有两个:1.根据上面的填充公式可知,若F为奇数那么填充值会出现小数。2.奇数维度的卷积核有中心便于指出对哪个像素点进行操作--中心指的那个点

# 进行卷积操作 cv2.filter2D
kernel = np.ones((5, 5), np.float32) / 25 # ones相当于啥都没做new = cv2.filter2D(img1, -1, kernel)  # 每个点都被周围平均了一下(除以25) 模糊了

image-20230606222935458.png

image-20230606223341496.png

一些其他的卷积核(滤波器)

轮廓检测
# 光看卷积核可知 其作用是将周围弱化(取值为-1) 将自己增强(取值为8) 对于一些本来就边缘跟周围像素点差别很大的地方 再进行这个操作 那么会更加的突出
kernel2 = np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]])  
new2 = cv2.filter2D(img1, -1, kernel2)
​

image-20230606224330946.png

锐化效果

跟卷积核有关

# 锐化操作 
kernel3 = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
new3 = cv2.filter2D(img1, -1, kernel3)
​

image-20230606224912404.png

7.2均值滤波与方盒滤波

方盒滤波

方盒滤波卷积核是固定的--是全一矩阵 此外还包括一个A值 用于控制操作效果

image-20230610105726410.png

# 方盒滤波 --其实也是做一个模糊的效果
# 方盒滤波的卷积核是固定的 全1卷积核 * A  (A其实就是 1/卷积核大小相乘)
# 不需要要手都创建卷积核 只需要告诉方盒滤波,卷积核大小是多少
# 当 normalize = True时 效果跟均值滤波是一样的
# -1 表示的是进行操作的图片深度 表示跟原图一致
dst = cv2.boxFilter(img1, -1, (5, 5), normalize=False)
​

方盒滤波未归一化后的操作效果

image-20230610105610579.png

均值滤波

输入结果为数组数字的均值 平均池化操作

对椒盐噪声效果比较好

# 均值滤波  ---对椒盐噪声效果比较好
blur = cv2.blur(img1, (5, 5))

image-20230610110225665.png

7.3高斯滤波

为什么叫做高斯滤波?卷积核里面的数字分布符合高斯分布

效果:进行高斯滤波后,该点的结果大部分来自其本身少部分来自周围并且离其越远 所占的比例就越小 符合钟型线 适合用于处理符合高斯分布的噪声 平滑

image-20230610111236363.png

高斯核计算过程

根据公式计算完每个坐标的权值后,获取到的是概率密度函数的值 需要确保总和为1 所以每个点还要除以权值总和得到最终的高斯核。

# 高斯滤波
# 为什么叫高斯滤波? 因为卷积核里面的数字是符合高斯分布的
# 参数解析
# 卷积核大小 x 轴的标准差  y轴标准差 默认是一样的
# 标准差越大 该点受周围影响就越多 越模糊  也跟卷积核大小有关 --越大 辐射范围越大
dst = cv2.GaussianBlur(img1, (5, 5), 5, 5)
​

8.day08

8.1中值滤波

原理:每次取中间的值作为卷积结果

一般用于处理椒盐噪声 效果最好

# 中值滤波  取中间值作为卷积后的结果
# 一般用于处理椒盐噪声
# 注意: 此时的size是数字 不是元组
dst = cv2.medianBlur(img1, 5)
​

8.2双边滤波

本质上其实就是高斯滤波 但是加上了一个灰度距离空间距离 在降噪的同时可以比较好的保留边缘信息从而达到“磨皮美颜”的效果

要理解!!!

image-20230610202322466.png

image-20230610202632847.png

# 双边滤波
# sigmaColor 是计算像素使用的sigma  灰度距离的sigma
# sigmaSpace 是计算空间信息的sigma
# 卷积核大小  sigmaColor  sigmaSpace
dst = cv2.bilateralFilter(img1, 7, sigmaColor=20, sigmaSpace=50)

8.3sobel算子

什么是算子? 相较于滤波用于降噪,而算子是用来提取图片特征信息、找图像边缘、边界的,本质上还是卷积操作

关键: 在进行边缘检测之前 一般都要对图片进行降噪操作以减少边缘检测错误

什么是边缘?是像素值发生跃迁(从一种颜色变换到另一种颜色的边界)的位置,是图像的显著特征之一。

原理:sobel算子对图像求一阶导数,导数越大,说明像素再该方向的变化越大,边缘信息越强

矩阵求导:离散数字采用离散差分算子(如下图) 计算(卷积运算)图像像素点亮度值的近似梯度

对于边缘检测其实都是对于但单通道的图片进行操作的,如果要对三通道图片进行操作,那么就要对其进行加权将三通道转为单通道的图片

image-20230613103543143.png

中间的000标识的是需要计算的值,其实就是计算一个离散的值往前后左右进行移动后获取到的值再减去原本的值,这个类似看成是一个梯度

sobel算子的实际效果其实是最差的 对于颜色变化不大的区域很难检测到,不怎么用

检测垂直方向边缘,在水平方向进行计算

# x轴方向 获取到的是垂直边缘
# 注意:sobel算子必须分别计算dx和dy 不能直接一起计算
dx = cv2.Sobel(img2,
               -1,  # 位深  可以直接写-1 也可以按照opencv的写法  dx和dy要么同时-1 要么同时opencv写法
               1,  # dx = 1 标识计算x轴方向的
               0,  # 只有有一个是1
               ksize=5)
dy = cv2.Sobel(img2, -1, 0, 1, ksize=5)
# 然后将图片进行相加
dst = cv2.add(dx, dy)
​

垂直方向

image-20230613105916987.png

水平方向边缘

image-20230613110117692.png

融合后

image-20230613105830137.png

8.4scharr算子

scharr算子是对sobel算子的一种改进 对于一些细小边缘的识别很好 但是需要注意的是 其大小只能是3*3的

scharr算子和sobel算子很类似,只不过采用了不同的卷积核的值,放大了像素变换的情况

image-20230613110949313.png

# scharr算子
# 是对sobel算子的一种改进 但是需要注意的是 其大小只能是3*3的
# scharr算子和sobel算子很类似,只不过采用了不同的卷积核的值,放大了像素变换的情况# 参数其实跟sobel算子是一样的 不过少了个核的大小 因为是固定的
dx = cv2.Scharr(img2,-1,1,0)
dy = cv2.Scharr(img2,-1,0,1)
dst = cv2.add(dx,dy)

image-20230613110949313.png

8.5拉普拉斯算子

在一阶导数的基础上对图像再进行一次求导,就可以发现 原来边缘处的二阶导数 = 0(边缘处的一阶导数是最大值的位置) 可以利用这个去找边缘 产生问题:二阶求导为0的位置有可能是无意义的位置

image-20230613111938767.png

dst = cv2.Laplacian(img2, -1, ksize=3)
# 容易受到噪声的影响 

image-20230707205526182.png

9.day09

9.1Canny边缘检测

Canny边缘检测算法被认为是最优的算法,评价的三个标准:1.低错误(减少噪声的影响) 2.高定位(与实际边缘接近) 3.最小响应(边缘只识别一次)

image-20230707213319426.png

非极大值抑制NMS

对于这些算出来的梯度,我们需要对其进行非极大值抑制,类似最大池化操作,其实就是找周围点的最大值

image-20230707214239054.png

通过下面这个例子,可以看出,在列上看,每列找出的那个最大值即黄色部分为抑制结果

image-20230707214401390.png

滞后阈值

对于非极大值抑制仍会产生一些问题,可能会有误判和漏判的情况,这时候就需要使用滞后阈值进行判定

我们设定两个阈值最大值阈值maxVal和最小阈值minVal,如果大于阈值那么肯定保留成为边界,如果小于阈值那么抛弃不成为边界,如果在这个中间区间内,如果跟边界相连那么就保留

如果看两个点是否相连呢?其实是空间上的相连。从该像素的8邻域看下有没有边界

image-20230707214741600.png

问题:对于阈值我们只能靠自己去猜去调整 调整起来比较困难,经验上是[100,200]这个区间内比较好

改进:自适应canny阈值

# 问题:对于阈值我们只能靠自己去猜去调整 调整起来比较困难
# 对于不同的阈值 会呈现不同的效果
dst1 = cv2.Canny(img2, 64, 128) # 数值调小一点 边缘检测更多  
dst2 = cv2.Canny(img2,100,200)
cv2.imshow("dst", np.hstack((dst1, dst2))) 

image-20230707220914816.png

9.2形态学概述

image-20230708163115518.png

9.3全局二值化

二值化:将图像的灭个像素变成两种值,比如0和255 (0是黑色 255是白色)

我们对其设定一个阈值thresh,如果大于该阈值那么设定为一个值否则设定为另一个值。

image-20230708163633918.png

image-20230708164253379.png

第三种其实就是抹平的操作,当像素值大于设定的阈值,那么就等于该阈值,如果小于该阈值那么就不用管。

# 对灰度图像进行操作的 别忘记了!!
gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 注意:会返回两个值,分别是阈值和结果
thresh, dst1 = cv2.threshold(gray, 25, 255, cv2.THRESH_BINARY)
cv2.imshow("dst", np.hstack((gray, dst1)))

image-20230708165239822.png

9.4自适应阈值二值化

根据图像上一小片区域计算其对应的阈值,在同一幅图像上的不同区域采用不同的阈值,从而使我们能在亮度不同的情况下的得到更好的结果。

image-20230708170135597.png

gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# 只有一个返回值
# 卷积核调的越大 其细节就越少 线条就越粗  如果C是正数表示阈值会降低 导致白色增多黑色减少 负数则相反***
dst = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, 0)
cv2.imshow("dst", np.hstack((gray, dst)))

image-20230708170912845.png

C设置为-1,结果如下所示

image-20230708171747602.png

9.5腐蚀操作

腐蚀操作也是用卷积核扫描图像,卷积核全为1(如果卷积操作结果不等于3 * 3 * 255 说明有黑色的像素点),寻找覆盖区域内最小值,用于代替锚点位置的像素值,所以只要卷积核范围内有黑的(像素值为0),那么该像素点就是黑的。只有范围内全是白色 那么才是白色的 看起来就是 白色被腐蚀掉了一些 。 可以用来去除图形的毛刺

image-20230708174246112.png

图中只有红线画出的区域的白色才能被保留,剩余区域全部被腐蚀了。

kernel = np.ones((3, 3), np.uint8)
dst = cv2.erode(img2, kernel, iterations=2)  # iterations表示腐蚀次数 img2是二值化图像
cv2.imshow("dst", np.hstack((img2, dst)))

image-20230708175152657.png

9.6获取形态学卷积核

image-20230708202717016.png

# kernel = np.ones((3, 3), np.uint8)
# 可以不用自己写卷积核啦 可以自动获取到
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))  # 卷积核的形状 不是指长宽 是指卷积核中1的形状
print(kernel)
# [[0 0 1 0 0]
#  [0 0 1 0 0]
#  [1 1 1 1 1]
#  [0 0 1 0 0]
#  [0 0 1 0 0]]
gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
dst1 = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, -1)
dst2 = cv2.erode(dst1, kernel, iterations=1)  # iterations表示腐蚀次数
cv2.imshow("dst", np.hstack((dst1, dst2)))

image-20230708210330047.png

9.7膨胀操作

膨胀操作是腐蚀操作的反操作。该像素点的值等于以该像素点为中心的3*3范围内的最大值。对于二值化图像来说,只要原图片3 * 3范围内有白的,该像素点就是白的。膨胀操作可以让白色区域变大 ,腐蚀会让白色区域变小。

# 膨胀操作
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))  # 卷积核的形状 不是指长宽 是指卷积核中1的形状
print(kernel)
# [[0 0 1 0 0]
#  [0 0 1 0 0]
#  [1 1 1 1 1]
#  [0 0 1 0 0]
#  [0 0 1 0 0]]
gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
dst1 = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, -1)
dst2 = cv2.dilate(dst1, kernel, iterations=2)
cv2.imshow("dst", np.hstack((dst1, dst2)))

image-20230708205816089.png

膨胀腐蚀操作一般是结合使用 ,先使用腐蚀将毛刺去除,然后再使用膨胀将字体大小还原。 image-20230708210600208.png

9.8开运算

什么是开运算?其实开运算和闭运算都是腐蚀和膨胀的基本应用

开运算 = 先腐蚀+后膨胀

记忆点: 看后面那个,膨胀相当于变大了即打开了--开运算

应用: 可以用于去除图形外的噪声

image-20230709203655335.png

kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))  # 卷积核的形状 不是指长宽 是指卷积核中1的形状
gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
dst1 = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3, -1)  # 自适应二值化图像
# 开运算
dst2 = cv2.morphologyEx(dst1, cv2.MORPH_OPEN, kernel, iterations=1)

image-20230709204303115.png

9.9闭运算

闭运算 = 先膨胀+后腐蚀

应用:可以去除图形内的噪声

# 与开运算相比 只是将模式改了
dst3 = cv2.morphologyEx(dst1,cv2.MORPH_CLOSE,kernel,iterations=1)

image-20230709205048773.png

9.10形态学梯度

梯度用于刻画目标边界或边缘位于图像灰度级剧烈变化的区域,形态学梯度根据膨胀或者腐蚀与原图作差组合来实现增强结构元素领域中像素的强度,突出高亮区域的外围 即边缘部分

常常将膨胀和腐蚀基础操作组合起来一起使用实现一些复杂的图像形态学梯度。可以计算的梯度常见如下四种(作为了解即可):

基本梯度:

基本梯度是用膨胀后的图像减去腐蚀后的图像得到差值图像,称为梯度图像也是OpenCV中支持的计算形态学梯度的方法,而此方法得到梯度有被称为基本梯度。

内部梯度:

是用原图像减去腐蚀之后的图像得到差值图像,称为图像的内部梯度。

外部梯度:

是用图像膨胀之后再减去原来的图像得到的差值图像,称为图像的外部梯度。

方向梯度: 方向梯度是使用X方向与Y方向的直线作为结构元素之后得到图像梯度,用X方向直线做结构元素分别膨胀与腐蚀之后得到图像求差值之后称为X方向梯度,用Y方向直线做结构元素分别膨胀与腐蚀之后得到图像求差值之后称为Y方向梯度。

# 这里获取到的形态学梯度是基本梯度即 膨胀-腐蚀
dst2 = cv2.morphologyEx(dst1, cv2.MORPH_GRADIENT, kernel, iterations=1)

image-20230709211740141.png

9.11顶帽操作

顶帽 = 原图 - 开运算 就可以获取到开运算后去除的噪点

dst2 = cv2.morphologyEx(dst1, cv2.MORPH_TOPHAT, kernel, iterations=1)

image-20230709214147951.png

9.12黑帽操作

黑帽 = 原图 - 闭运算

dst3 = cv2.morphologyEx(dst1, cv2.MORPH_BLACKHAT, kernel, iterations=1)

10.day10

10.1查找和绘制轮廓

什么是图像轮廓? 图像轮廓指的是具有相同颜色或灰度的连续点的曲线(其实就是边缘)轮廓在形状分析和物体检测和识别中很有用。

轮廓的作用:

  1. 用于图形分析
  2. 物体的识别和检测

注意点:

1.为了检测的准确性,需要对图像进行二值化或者canny操作

2.绘制轮廓回修改输入的图像,所以需要对原图像进行备份

查找轮廓

image-20230710163750167.png

image-20230710164109374.png

不用太纠结这个索引

image-20230710164202271.png

image-20230710204528644.png

这个索引使用的最多

image-20230710205828296.png

image-20230710205038735.png

# 查找轮廓
# 返回两个结果 轮廓和层级
contours, hierarchy = cv2.findContours(dst1, 3, cv2.CHAIN_APPROX_NONE)
# 打印轮廓
print(contours)
# 想要获取哪个图片 那么我们就根据索引进行获取即可
print(len(contours))  # 轮廓的个数
print(contours[1])  # 获取指定索引的轮廓
绘制轮廓

image-20230710211454870.png

# 绘制轮廓 绘制轮廓会直接修改原图 要进行备份
img_copy = img.copy()
# img = np.zeros((640, 640, 3), np.uint8)
# 轮廓颜色按照 BGR 这个顺序
cv2.drawContours(img_copy, contours, -1, (0, 0, 255), 2)
cv2.imshow("img", img)
cv2.waitKey()

原图:

image-20230710212113704.png

绘制轮廓后:

image-20230710212134786.png

10.2计算轮廓面积和周长

image-20230710212520534.png

# 计算轮廓面积和周长
# 1.计算面积
# 只能一个轮廓一个轮廓的计算
area = cv2.contourArea(contours[-1])  # 单位是像素 这里不用指定是闭合或者是开放 因为面积肯定要闭合才能计算呢
print("area:", area)
# 2.周长
# 当closed=True时,函数会将轮廓视为封闭的曲线,即首尾相连形成一个闭合形状。
# 例如,如果你有一个正方形的轮廓,设置closed=True将返回正方形的周长值。
# 当closed=False时,函数会将轮廓视为非封闭的曲线,即首尾不相连。
# 例如,如果你有一个线段的轮廓,设置closed=False将返回线段的长度。
perimeter = cv2.arcLength(contours[-1], closed=False)  # 简单来说,如果你要绘制的轮廓确实是封闭的 那就写true 否则写false 根据实际情况进行调整即可
print("perimeter:", perimeter)

10.3多边形逼近

findContours后获取到的轮廓可能过于复杂不平滑,可以是使用函数用多边形去近似逼近这个曲线做适当近似。

DP算法原理:

如果我们有轮廓A点到B点找一个近视多边形。首先我们将AB相连,设定一个阈值T,然后找出AB轮廓上的点C到AB连线的最大值,如果最大值大于我们设定的阈值,那么ABC构成新的多边形,然后继续找其余点到ABC多边形的距离的最大值,不断循环往复即可获取到近似多边形。

image-20230710220156756.png

# 多边形逼近 阈值越大 多边形就越粗糙
appDP = cv2.approxPolyDP(contours[-1], 20, closed=False)
# appDP 本质上是一个轮廓数据
# 绘制近似逼近的轮廓
img_copy = img.copy()
#  是一个列表才行(表示全部的轮廓数据但是我们只有一个所以我们进行这样的操作) 此时 绘制的索引填 0
cv2.drawContours(img_copy, [appDP], 0, (0, 0, 255), 1)
cv2.imshow("img_copy", img_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()

10.4凸包

凸包指的是完全包含原有的轮廓,并且仅在轮廓上的点所构成的多边形,并且凸包任意一点都是凸的,凸包内任意两点连接的直线都在凸包内部。凸包上的点其实就是多边形逼近上的点。

image-20230711100415691.png

# 凸包必须要先找到 轮廓 才能进行凸包
# 计算凸包
hull = cv2.convexHull(contours[0]) # 输入要进行凸包的轮廓
# 画出凸包
cv2.drawContours(img, [hull], 0, (0, 0, 255), 1)

绿色部分为多边形逼近,蓝色部分为凸包效果。

image-20230711101252660.png

10.5最小外接矩形和最大外接矩形

image-20230711101846175.png

image-20230711102026417.png

# 最大和最小外接矩形
min_rect = cv2.minAreaRect(contours[0])  # 要注意这个到底是不是我们需要的轮廓
print("min_rect: ", min_rect)  # 是一个旋转的矩形 包含矩形的 左上角坐标、长宽、旋转角度
max_rect = cv2.minAreaRect(contours[0])
print("max_rect: ", max_rect)
​
# 绘制旋转框一般都使用这个函数boxPoints
box = cv2.boxPoints(min_rect)  # 其实就是将旋转矩阵的四个坐标点给计算出来啦 相当于是一个轮廓了呀
# 注意:这里的数据是浮点数据 如果是绘制的话 不能是浮点型的 四舍五入
box = np.round(box).astype('int64')
print("box:", box)
cv2.drawContours(img, [box], 0, (0, 0, 255), 1)
​
cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 最大外接矩形--注意:是没有旋转角度的
x, y, w, h = cv2.boundingRect(contours[0])
cv2.rectangle(img, (x, y), (w + x, h + y), (255, 0, 0), 2, 4)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

10.6图像金字塔

image-20230711104319546.png

image-20230711104921200.png

高斯金字塔

高斯金字塔是通过高斯平滑亚采样获得的一系列上/下采样图像。

下采样

要注意的是这里的分数应该是1/256(高斯概率分布的总和必须是1)

image-20230711105304904.png 注意:这里我们将偶数行和偶数列去掉,去掉的数据即为丢失掉的信息。

# 下采样
img = cv2.imread("../resource/IMG_01.jpg")
print(img.shape)  # (1080, 1920, 3)
# 下采样操作
dst = cv2.pyrDown(img)
print(dst.shape)  # (540, 960, 3)
cv2.imshow("dst", dst)  # 下采样一次 图片大小变为原来的1/2 如果是奇数的shape也可以进行这个操作
cv2.waitKey(0)
cv2.destroyAllWindows()

上采样

向上采样是先填充,然后用放大四倍的卷积核进行卷积操作,获取近似值。

# 上采样
dst2 = cv2.pyrUp(img)
print(dst2.shape) # (2160, 3840, 3)
cv2.imshow("dst2", dst2)  # 下采样一次 图片大小变为原来的1/2 如果是奇数的shape也可以进行这个操作
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230711111616541.png

img = cv2.imread("../resource/IMG_01.jpg")
print(img.shape)  # (1080, 1920, 3)
# 下采样操作
dst = cv2.pyrDown(img)
print(dst.shape)  # (540, 960, 3)
cv2.imshow("dst", dst)  # 下采样一次 图片大小变为原来的1/2 如果是奇数的shape也可以进行这个操作
cv2.waitKey(0)
cv2.destroyAllWindows()
​
# 上采样
dst2 = cv2.pyrUp(img)
print(dst2.shape) # (2160, 3840, 3)
cv2.imshow("dst2", dst2)  # 下采样一次 图片大小变为原来的1/2 如果是奇数的shape也可以进行这个操作
cv2.waitKey(0)
cv2.destroyAllWindows()
拉普拉斯金字塔

先进行下采样再进行上采样然后用原始图像减去采样后的结果图像即可获取到拉普拉斯金字塔图像。得到的结果就是我们进行采样后缺失的部分

image-20230711130016293.png

​
print(img.shape)  # (1080, 1920, 3)
# 先缩小
dst = cv2.pyrDown(img)
# 再放大
dst = cv2.pyrUp(dst)
# 原图减去
dst = img - dst  # 这个就是第0层的拉普拉斯金字塔
print(dst.shape)  # 大小应该不变
cv2.imshow("dst", dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
​
# 如果是第一层 那么是拿dst继续缩小放大 重复上面的步骤即可
dst = cv2.pyrDown(dst)
# 再放大
dst = cv2.pyrUp(dst)
# 原图减去
dst = img - dst  # 这个就是第0层的拉普拉斯金字塔
cv2.imshow("dst", dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230711131710079.png

11.day11

11.1图像直方图

图像直方图是一种用于表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的数量,用于直观了解图片亮度分布。

横坐标:图像各个像素点的灰度级(图片中像素值的所有取值)

纵坐标:具有该灰度级的像素的个数

image-20230712173156168.png

归一化直方图

与上面的直方图相比,归一化直方图的纵坐标变成了灰度级出现的概率

image-20230712173413037.png

直方图术语

dims :需要统计的特征的数目。例如,如果我们只统计灰度值那么dims = 1,如果我们要统计三通道,那么dims = 3。

bins:每个特征空间子区段的数目。简单来说就是,直方图中柱子的数目

image-20230712174334654.png

image-20230712201237039.png

range:统计灰度值的范围,一般是0-255

image-20230712202336730.png

# 图像直方图进行统计
# 可以对多个图片进行统计 所以要使用[]
# 如果输入的图像是灰度图 那么填写指定通道的时候只能是[0]
dst = cv2.calcHist([img1], [0], None, [64], [0, 255])
print(dst.shape)

11.2绘制直方图

# 绘制图像直方图数据
# 方法一
# 变成灰度图片
gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
# 统计直方数据
plt.hist(gray.ravel(), bins=256, range=[0, 255])  # gray.ravel()变为一维数据
plt.show()
​
# 方法二
b = cv2.calcHist([img1], [0], None, [64], [0, 255])
g = cv2.calcHist([img1], [1], None, [64], [0, 255])
r = cv2.calcHist([img1], [2], None, [64], [0, 255])
plt.plot(b, color='b')
plt.plot(g, color='g')
plt.plot(r, color='r')
plt.show()

image-20230712205312537.png

image-20230712211556771.png

11.3直方图均值化

是通过拉伸像素强度的分布范围,使得其在0-255灰阶上的分布更加的均衡,提高了图像的对比度(如果像素大小都集中在一个范围内,那么色彩较为一致,没有明显的强弱,对比度就不行啦),达到改善图像主管视觉效果的目的,对比度较低的图像可以使用均值化的方法来增强图像细节。

image-20230712205636090.png

原理:

1.计算累计直方图

2.将累计直方图进行区间转换

3.在累计直方图中,概率相近的原始值会被处理为相同的值

获取累计直方图

关键:当前像素值百分比等于前面的百分比加上当前像素百分比。

image-20230712210135209.png

直方图转化

原来的累计直方图乘上像素值个数即可得到均衡直方图

image-20230712210333853.png

gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
# 变黑
gray_dark = gray - 40
# 变亮
gray_bright = gray + 40
# 均衡化
dst = cv2.equalizeHist(gray_dark)
cv2.imshow("dst", np.hstack((gray_dark, dst))) # 可以看到 进行均衡之后获取到更多的细节
cv2.waitKey(0)
cv2.destroyAllWindows()
​
# 直方图显示
plt.hist(dst.ravel(), bins=256, range=[0, 255])  # gray.ravel()变为一维数据
plt.show()

image-20230712211247369.png

image-20230712211641659.png

11.4使用掩膜的直方图

什么是掩膜?

image-20230712212214302.png

如何生成掩膜?

1.先生成一个跟图片大小一样的全黑的图片 mask = np.;zeros(gray.shape,np.uint8)

注意:这里的shape根据需求可以进行修改

2.将想要的区域通过索引方式设置成255 mask[100:200,200:300] = 255

gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
# 使用掩膜进行直方图统计
mask = np.zeros(gray.shape, np.uint8)
# 根据需要的区域进行索引 设置为白色
mask[200:400, 300:500] = 255
​
cv2.imshow("mask", mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
​
# 如何使用掩膜应用到图像上呢 gray和gray先做与运算(结果是其本身) 再与mask做与运算(只要有一个是0 那么就是0)
cv2.imshow("mask_gray", cv2.bitwise_and(gray, gray, mask=mask))
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230712213614603.png