计算机视觉知识点整理(上) 基础篇(持续更新)

  • Post author:
  • Post category:其他


前言

最近在面试,每天会被考到很多知识点,这些知识点有些我已经看了十几遍,还是会反应慢或者记不住。回想我在学习过程中,也是学了忘忘了学,没有重复个几十遍根本难以形成永久记忆。这次我复习和整理面试知识点的时候决定把CNN里面的关键创新点、容易疏忽的点都记录下来,方便快速查找回顾,于是就有了这篇像词典一样的永久更新的文章。

因为知识点过多,导致文章过长,目前已拆分为三篇:

1.

基础知识篇

2.

轻量化网络篇

3.

通用模型篇

一.基础知识

1.1 BatchNorm/LayerNorm/InstanceNorm/GroupNorm

基础知识点 记忆点 备注
Batch Norm 达到的效果 BatchNorm就是在深度神经网络训练过程中使得每一层神经网络的输入保持相同分布。
出发点

解决covariate shift:如果ML系统实例集合<X,Y>中的输入值X的分布老是变,这不符合IID假设,网络模型很难稳定的学规律。

问题:分布变化=>非线性输出向两端移动=>梯度消失=>网络收敛慢

解决:BN=>将隐藏层的输入拉回到(0,1)正态分布=>使激活值落在非线性区域=>使得梯度变大=>加快网络收敛

保障非线性

BN为了保证非线性的获得,对变换后的满足均值为0方差为1的x又进行了scale加上shift操作(y=scale*x+shift)

核心思想应该是想找到一个线性和非线性的较好平衡点,既能享受非线性的较强表达能力的好处,又避免太靠非线性区两头使得网络收敛速度太慢。

推理时的参数

推理的时候可能bs是1,那么α,γ,μ和σ从何而来呢?

参数α和γ是最后收敛的参数,而μ和σ则在训练的时候记住每个batch内的参数,然后求出平均值和方差的期望,这样在全局上估计的这组参数更加准确。

如何减少这些参数的存储量?

可以采用训练收敛最后几批mini batch的 μ和σ的期望,作为预测阶段的μ和σ

正则化作用
在BN层中,每个batch计算得到的均值和标准差是对于全局均值和标准差的近似估计,这为我们最优解的搜索引入了随机性,从而起到了正则化的作用。
BN的缺陷 带有BN层的网络错误率会随着batch_size的减小而迅速增大,当我们硬件条件受限不得不使用较小的batch_size时,网络的效果会大打折扣。
BN/LN/IN/GN 示意图
均值方差的作用位置
  • BN:固定C,对H,W和N求均值方差。
  • LN:固定N,对C和H,W求方差。
  • IN:固定C和N,对H,W求均值和方差。
  • GN:固定N,对C分组,每组内对C’,H,W求均值和方差。
相比BN为什么好? LN/IN和GN都没有对batch作平均,所以当batch变化时,网络的错误率不会有明显变化
经验表现 LN和IN 在时间序列模型(RNN/LSTM)和生成模型(GAN)上有很好的效果,而GN在视觉模型上表现更好。

1.2 关于dropout

基础知识点 记忆点 备注
dropout 概念

在每个训练批次中,神经元的激活值以一定的概率p停止工作

目的 起到正则化作用,可以使模型泛化性更强,因为模型不会太依赖某些局部的特征。
示意图

面试问到是冻结权重还是冻结神经元? 答冻结神经元。

为什么缓解过拟合?

为什么有人说dropout类似model ensemble的效果?

  1. dropout每次隐藏部分神经元,就像是在训练不同的模型,因为每次网络的结构都不同。整个dropout就像是在对多个模型进行了ensemble。不同模型可能产生不同的过拟合,互相之间进行ensemble,就会起到平均效果更好。
  2. dropout有可能减少密集的局部连接,迫使模型去学习更为全局更为robust的特征,而模型不应该因为局部的细节而产生大的变化。

二.常见难点解决思路

2.1 如何解决小目标的检测问题

如何解决小目标的检测问题
方法 知识点 备注
输入数据 增大采集图像分辨率 相当于给框增加更为丰富的信息,以便提取到更复杂的特征。
增大模型图像输入分辨率 增大输入分辨率来提升小目标的检测能力。
模型与模块 图像金字塔 MTCNN使用了图像金字塔;缺点是多次特征提取,慢。
FPN/PAN/BiFPN 一次forward就可以识别多个尺度,可以提升小尺度的识别。
ASPP/SPP/RFBNet 不同尺度的特征融合,在扩大感受野的同时,也融合多个尺度的特征,增强了模型对于小目标的检测能力。
2-stage目标检测 ROI pooling => ROI Align  ,因为ROI pooling对于小目标的定位损失是巨大的。
Perceptual Generative Adversarial Networks for Small Object Detection Perceptual Generative Adversarial Networks for Small Object Detection中用使用感知生成式对抗网络(Perceptual GAN)提高小物体检测率,generator将小物体的poor表示转换成super-resolved的表示,discriminator与generator以竞争的方式分辨特征。Perceptual GAN挖掘不同尺度物体间的结构关联,提高小物体的特征表示,使之与大物体类似。包含两个子网络,生成网络和感知分辨网络。生成网络是一个深度残差特征生成模型,通过引入低层精细粒度的特征将原始的较差的特征转换为高分变形的特征。分辨网络一方面分辨小物体生成的高分辨率特征与真实大物体特征,另一方面使用感知损失提升检测率。在交通标志数据库Tsinghua-Tencent 100k及Caltech上实验。
Data Augmentation 《Augmentation for small object detection》
  1. 小目标的over sampling。
  2. 针对同一张图片里面包含小目标数量少的问题,在图片内用分割的Mask抠出小目标图片再使用复制粘贴的方法(当然,也加上了一些旋转和缩放,另外要注意不要遮挡到别的目标)。
anchor anchor-based
  1. 增加小anchor(配合FPN)。
  2. 降低小目标过滤阈值。
自适应Anchor阈值调整
finetune 对小分辨率单独finetune 在正常数据上做训练,再在小目标上做微调。
一种自己独创的方法 根据mask来识别小目标,做loss加权
  1. 对mask图使用opencv来标记轮廓。
  2. 对每个轮廓计算面积。
  3. 对面积小于阈值的标记为小物体,并且填充特殊的flag像素值。
  4. 针对得到的新mask做交叉熵加权损失。
import cv2
import numpy as np
import random
import torch
import matplotlib.pyplot as plt
AREA_THS = 30

def mask_small_object(mask, area_thresh, pixels=[0, 1, 2], weights=[0.5, 1.0, 10.0]):
    """
    :param mask: 输入mask掩码
    :param area_thresh: 小物体的阈值,面积小于该值认为是小物体
    :param pixels: 背景 小物体 大物体的mask值
    :param weight: 背景,前景大物体,前景小物体的权重
    :return: 权重图
    """


    contours, hierarchy = cv2.findContours(mask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    area = []
    for i in range(len(contours)):
        area.append(cv2.contourArea(contours[i]))

    for i in range(len(area)):
        if area[i] < area_thresh:
            cv2.fillPoly(mask, [contours[i]], (2, ))
    for i in range(len(pixels)):
        mask[mask==[pixels[i]]] = weights[i]
    return mask

def mask_cross_entropy(y, y_hat, weight):
    n = 1e-7
    # return -np.sum(y * np.log(y_hat + n) + (1 - y) * np.log(1 - y_hat + n), axis=1)
    assert y.shape == y_hat.shape
    entro = (y * np.log(y_hat + n) + (1 - y) * np.log(1 - y_hat + n))
    mask_entro = entro * weight
    res = -np.mean(mask_entro)
    return round(res, 5)

if __name__ == '__main__':
    AREA_THS = 30
    img = np.zeros(shape=(512, 512, 3), dtype=np.uint8)
    label = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    for i in range(10):
        x, y = random.randint(10, 500), random.randint(10, 500)
        r = random.randint(1, 10)
        cv2.circle(label, (x, y), radius=r, color=(1), thickness=-1)
    weight = mask_small_object(label, area_thresh=AREA_THS)
    pred = np.abs(np.random.rand(512, 512))
    print(mask_cross_entropy(label, pred, weight))

三.常见loss函数

loss函数 记忆点 备注
CE 交叉熵的推理

1.信息量的表示

2.熵是信息量的期望

3.相对熵(KL散度)表示两个分布的差异

4.交叉熵是相对熵的数学变形

前面是p(x)的熵,是一个常量。后面就是交叉熵。

loss的形式
分类loss为什么prefer交叉熵than MSE? MSE不能保证误差越大,梯度越大,学习越快。而交叉熵可以,因此收敛更好更快。
Focal 解决的问题

1.解决类别不平衡

2.难易样本分布不平衡

loss的形式
loss的参数

基于交叉熵演变而来。

γ用来调节样本难易程度,一般取2。γ提升了预测与GT差距大的样本对loss的贡献比(困难样本)。

α用来调节样本类别的比例,默认α=0.25,将前景的loss放大而背景的loss缩小。

Dice 解决的问题 语义分割中正负样本不平衡
loss的形式

Dice系数:

Dice loss:

Laplace Smoothing:

Dice的优势劣势
  1. GIOU paper 提出给定优化指标本身与代理损失函数之间的选择,最优选择就是指标本身。所以Dice本身更贴合语义分割常用的评价指标iou。
  2. Dice的问题是求导在极端情况下会导致梯度陡增,使训练难以收敛震荡,不稳定。

四.常见衡量指标

指标名称 记忆点 备注
MAP(目标检测) TP、TN、FP、FN
Precision & Recall Precision = TP / (TP + FP)   Recall = TP / (TP + FN)
AP

按照模型给出的置信度,对每个类的所有预测框进行排序:

逐个计算Precision 和 Recall,绘制PR曲线,AP就是PR曲线上的Precision值求均值。

实际应用中就会对PR曲线最做平滑:

MAP MAP就是对所有类的AP做平均值。
MIOU(语义分割)

IOU

IOU的定义:计算真实值和预测值两个集合的交集和并集之比

IOU=TP/(FP+FN+TP)

MIOU 对于不同类别的IOU求平均值
MIOU的数学表达

pij表示真实值为i,被预测为j的数量, K+1是类别个数(包含空类)。pii是真正的数量。pij、pji则分别表示假正和假负。

补充篇一:opencv(python)知识点

0.1 图像基础知识点

图像&opencv基础知识点
基础知识点 记忆点 备注
图像基础知识 数字图像 数字图像又称数码图像或者数位图像,是二维图像用有限数字数值像素的表示,由数组或者矩阵表示。数字图像可以理解为一个二维函数f(x, y),其中x,y是空间的坐标,而任意位置的幅值f称为图像在该点的强度或者灰度。

常见的电磁波成像特点

(按照频率从高到低)

  • γ射线成像:波长最短,频率最高,是由原子核内发射出来的电磁波。放射性物质或者原子核反应中常有这种辐射。γ射线穿透力强,对生物破坏性大。
  • x射线成像:CT就是用x射线照射物体。由于生物组织或者工程组件的不同部位对x射线的吸收率不同,从而得到不同的衰减以成像(密度越高,吸收的越多)。
  • 紫外线成像:具备化学效应和荧光效应,常用于生物医学。
  • 可见光波段成像
  • 红外线成像:一切物体都可以辐射出红外线,可利用探测仪测量目标本身与背景间的红外线差得到红外图像。
  • 微波成像:用于雷达以及生成地表情报图。
  • 射频成像:电视、无线电、手机的波段。也可以用于医学,比如磁共振。
常见的图像格式
读取图像
# cv2读取 & 直接呈现
img = cv2.imread("../test.jpg", flags=1)
print(img.shape)
cv2.imshow("test.jpg", img)
key = cv2.waitKey(0)
if key == ord('q'):
    cv2.destroyAllWindows()
elif key == ord('s'):
    cv2.imwrite("test_bak.jpg", img)

# cv2读取后需转换为RGB,再由matplotlib呈现
img = cv2.imread("../test.jpg", flags=1)
print(img.shape)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
print(img.shape)
plt.figure(figsize=(12, 6))
plt.imshow(img)
plt.show()

# PIL 读取
img = Image.open("../test.jpg")
print(img)
print(np.asarray(img).shape)

# matlpotlib读取
img = plt.imread("../test.jpg")
print(img.shape)
  • flags=0代表灰度图,flages=1代表彩图,默认是三通道彩图。
  • cv2.imread()、PIL.Image.open()、matplotlib.imread()读取的默认shape都是【H,W,C】,与pytorch的默认顺序不同,需要转换为【C, H, W】。
  • opencv读取是默认的BGR,直接用opencv呈现是没问题的。但是后面处理或者用matplotlib呈现就需要转换为RGB。
  • 灰度 = B * 0.114 + G * 0.587 + R * 0.299
图像直方图

图像直方图是以表示数字图像中亮度分布的直方图,表达了图像亮度的整体分布。常用来二值化。

img = cv2.imread("../test.jpg", flags=1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hist = cv2.calcHist(img, channels=[0], mask=None, histSize=[256], ranges=[0, 256])
plt.figure(figsize=(12, 6))
plt.plot(hist)
plt.xlabel("bins")
plt.ylabel("num of pixels")
plt.title("histgram of grayscale")
plt.show()

颜色空间
  • RGB:依据人眼的颜色空间。有RGB三通道,范围都是0-255。
  • HSV:H(色调),代表色彩;S(饱和度),取值是0-100%,值越大,颜色越饱和;V(明度),从0%(黑)到100%(白)。
  • HSI:H(色调),S(饱和度),I(强度)。
  • CMYK:C(青)、M(品红)、Y(黄),常用于印刷行业。

opencv中颜色空间的改变也是cv2.cvtcolor()

图像基础处理 opencv绘图
# 1,绘制线段
if show_control == 1:
    img = np.zeros((512, 512, 3))
    cv2.imshow("origin photo", img)
    cv2.waitKey(2000)
    cv2.line(img, (0, 0), (250, 250), color=(0, 0, 255), thickness=1)
    cv2.imshow("photo with a line", img)
    cv2.waitKey(2000)
    cv2.destroyAllWindows()

#2. 绘制矩形 需要左上角 右下角的坐标
if show_control == 2:
    img = cv2.imread("../test.jpg", flags=1)
    cv2.rectangle(img, (251, 402), (373, 464), color=(0, 0, 255), thickness=2)
    cv2.putText(img, "bobbies", (253, 400), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(25, 0, 0), thickness=2)
    cv2.imshow("rectangle photo", img)
    cv2.waitKey(2000)
    cv2.destroyAllWindows()

#3. 绘制圆形 需要圆心和半径
if show_control == 3:
    img = np.zeros((512, 512, 3))
    cv2.circle(img, (100,100), 20, color=(0, 0, 255))
    cv2.imshow("circle", img)
    cv2.waitKey(2000)
    cv2.destroyAllWindows()

图像平移
img = cv2.imread("../test.jpg")
# x方向移动多少, y方向移动多少
H = np.float32([[1, 0, 50], [0, 1, 25]])
rows, cols = img.shape[: 2]
# 注意这里rows和cols需要反置,先列后行
res = cv2.warpAffine(img, M=H, dsize=(cols, rows))
cv2.imshow("origin pic", img)
cv2.imshow("affine pic", res)
cv2.waitKey(0)

图像缩放

常见的插值方法:

  • 最近邻插值:效果不好,放大后有严重的马赛克,缩小后的图像有严重的失真。

  • 线性插值

  • 三次样条插值
  • LANCZOS插值(效果还不错)
图像旋转
# 旋转
if control == 2:
    img = cv2.imread("../test.jpg")

    h, w  = img.shape[:2]
    # scale >0 逆时针旋转  scale<0 顺时针旋转 angle旋转角度
    H = cv2.getRotationMatrix2D(center=(h/2, w/2), angle=45, scale=0.5)

    res = cv2.warpAffine(img, M=H, dsize=(w, h))

    cv2.imshow("rotation", res)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

仿射变换
if control == 3:
    img = cv2.imread("../test.jpg")

    h, w  = img.shape[:2]
    # 用三个点来确定仿射变换
    pos1 = np.float32([[50, 50], [200, 50], [50, 200]])
    pos2 = np.float32([[10, 100], [200, 50], [100, 250]])
    H = cv2.getAffineTransform(pos1, pos2)

    res = cv2.warpAffine(img, M=H, dsize=(w, h))

    cv2.imshow("affine", res)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

透视变换
img = cv2.imread("../test.jpg")
h, w = img.shape[:2]
pos1 = np.float32([[114, 82],[287, 156], [8, 100], [143, 177]])
pos2 = np.float32([[0, 0], [188, 0], [0, 262], [188, 262]])

H = cv2.getPerspectiveTransform(pos1, pos2)
#图像透视变换
result = cv2.warpPerspective(img, H, dsize=(w, h))
cv2.imshow("origin pic", img)
cv2.imshow("warp pic", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像滤波操作 滤波基本原理
  • 基本原理:图像可以看作是二维的信号,像素点的灰度值代表着信号的强弱。图像中变化强烈的部分称为高频部分,图像中变化缓慢的部分称为低频部分。可以根据图像的高低频,设置高通或者低通滤波器。高通可以检测变化尖锐明显的部分,低通可以让图像变得平滑,消除噪声。高通滤波器常用于边缘检测,低通滤波器常用于图像平滑去噪。
方框滤波
img = cv2.imread("../test.jpg", flags=1)
# 不加normalize会使得很多pixel超过255 导致全图变白
filter = cv2.boxFilter(img, ddepth=-1, ksize=(3, 3), normalize=True)
cv2.imshow("original", img)
cv2.imshow("box filter", filter)
cv2.waitKey(0)
cv2.destroyAllWindows()

kernel内元素全部是1。

注意进行normalize,否则图像会因为超过255,变白,另外kernel越大,模糊的效果越明显。

均值滤波(平均模糊)
if control == 2:
    img = cv2.imread("../test.jpg", flags=1)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    blur = cv2.blur(img, ksize=(3, 3))
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(blur)
    plt.show()

kernel内元素全部是1。

等同于加了normalize的方框滤波。

高斯滤波
if control == 3:
    img = cv2.imread("../cat.jpg", flags=1)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    blur = cv2.GaussianBlur(img, ksize=(3, 3), sigmaX=0)
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(blur)
    plt.show()

高斯滤波卷积核内参数并不相同,而是呈现高斯分布的,中间高,两边低。高斯滤波可有效去除高斯噪声,保留更多的图像细节。

中值滤波

中值滤波是一种非线性滤波,是用像素点邻域灰度的中值代替该点的灰度值。,中值滤波可以取出椒盐噪声和斑点噪声。

if control == 4:
    img = cv2.imread("../luna.jpg", flags=1)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    blur = cv2.medianBlur(img, ksize=3)
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(blur)
    plt.show()

双边滤波

双边滤波是一种非线性滤波,是结合像素的空间邻近度和像素值相似度的一种折中处理。同时考虑空间信息和灰度的相似性,达到保边去噪的目的。具有简单、非迭代、局部处理的特点。双边滤波可以有效保证边缘信息。

# 双边滤波
if control == 5:
    img = cv2.imread("../luna.jpg", flags=1)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    blur = cv2.bilateralFilter(img, d=-1, sigmaColor=15, sigmaSpace=10)
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(blur)
    plt.show()

图像增强 灰度直方图均匀化

直方图均匀化就是将原图像经过某种变化,得到一副灰度直方图均匀的图像。直方图均匀化的思想就是对图像中像素个数较多的灰度级进行展宽,从而对像素较少的灰度级进行缩减。从而达到清晰图像的目的。工业上常用来解决过曝的情况。

img = cv2.imread("../dark_girl.jpg",flags=0)
print(img.shape)
img_equal = cv2.equalizeHist(img)
plt.figure(figsize=(12, 6))
plt.subplot(121)
plt.title("origin")
plt.imshow(img)
plt.subplot(122)
plt.title("equalize")
plt.imshow(img_equal)
plt.show()

彩色直方图均匀化
if control == 2:
    img = cv2.imread("../dark_girl.jpg")
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    r, g, b = cv2.split(img)
    equal_r = cv2.equalizeHist(r)
    equal_g = cv2.equalizeHist(g)
    equal_b = cv2.equalizeHist(b)
    img_equal = cv2.merge((equal_r, equal_g, equal_b))
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.title("origin")
    plt.imshow(img)
    plt.subplot(122)
    plt.title("equalize")
    plt.imshow(img_equal)
    plt.show()

Gamma变化

Gamma变化是对输入图像灰度值进行的非线性操作。使输出图像的灰度值和输入图像的灰度值呈现指数的关系。

Gamma变化用来进行图像增强,让图像从曝光强度的线性响应变得更接近人眼感受的响应。既对相机曝光或者光照不足的情况进行调节。

img = cv2.imread("../guobao.jpg")

def adjust_gamma(image, gamma=1.0):
    invGamma = 1.0 / gamma
    table = []
    for i in range(256):
        table.append(((i / 255) ** invGamma) * 255)
    table = np.array(table).astype("uint8")
    return  cv2.LUT(image, table)

def equalize(img):
    b, g, r = cv2.split(img)
    equal_r = cv2.equalizeHist(r)
    equal_g = cv2.equalizeHist(g)
    equal_b = cv2.equalizeHist(b)
    img_equal = cv2.merge((equal_b, equal_g, equal_r))
    return img_equal

img_gamma = adjust_gamma(image=img, gamma=0.35)
img_equalize = equalize(img=img)
cv2.imshow("original", img)
cv2.imshow("gamma", img_gamma)
cv2.imshow("equalize", img_equalize)
cv2.waitKey(0)
cv2.destroyAllWindows()

形态学操作 什么是形态学? 形态学是最为常见的图像技术,主要从图像中提取对表达和描绘区域形状有意义的图像分量,使后续的识别工作能够抓住目标对象最为本质的形状特征,如边界和连通区域。
腐蚀

针对图像的白色部分,膨胀进行扩张,白色区域变大,腐蚀进行收缩,白色区域变小。

腐蚀:用模板B来腐蚀A就是用B来逐步卷积A,模板B有一个锚点和锚框,如果锚点落在前景内,且锚框都落在前景内,则保留锚点像素。如果锚点落在前景内,锚框没有完全包含在前景内,则去除锚点像素改为背景。

img = cv2.imread("../erode.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_erode = cv2.erode(img, kernel=(3, 3))
plt.figure(figsize=(12, 6))
plt.subplot(121)
plt.imshow(img)
plt.subplot(122)
plt.imshow(img_erode)
plt.show()

膨胀

腐蚀的对偶运算。

同样使用模板B来卷积A,模板内包含锚点和锚框。如果锚框和前景有交集,如果此时锚点对应着背景,那么就把背景膨胀为前景。

开运算

先腐蚀后膨胀,把细微连在一起的物体分开,把物体的表面进行平滑。

换句话说开运算能够去除孤立的小点,毛刺和小桥,但总的位置和形状不变。

if control == 3:
    img = cv2.imread("../mor-open.jpg")
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(5, 5))
    img_erode = cv2.morphologyEx(img, op=cv2.MORPH_OPEN, kernel=kernel, iterations=1)
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(img_erode)
    plt.show()

闭运算

先膨胀运算再腐蚀运算。

闭运算能够将两个细微连接的图封闭到一起,可以去除孔洞和弥补小裂缝,并且保持整体的位置形状不变。

if control == 4:
    img = cv2.imread("../mor-close.jpg")
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(7, 7))
    img_erode = cv2.morphologyEx(img, op=cv2.MORPH_CLOSE, kernel=kernel, iterations=3)
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(img_erode)
    plt.show()

形态学梯度
顶帽和黑帽
  • 顶帽:原图像与开运算的插值,突出原图像中比周围亮的区域。
  • 黑帽:闭运算与原图像差值,突出原图像比周围暗的区域。
# 形态学梯度  膨胀-腐蚀
if control == 5:
    img = cv2.imread("../haizei.jpg")
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(7, 7))
    img_erode = cv2.morphologyEx(img, op=cv2.MORPH_GRADIENT, kernel=kernel, iterations=1)
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(img_erode)
    plt.show()

# 顶帽 原图-开运算  突出原图中亮的地方
if control == 6:
    img = cv2.imread("../haizei.jpg")
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(9, 9))
    img_erode = cv2.morphologyEx(img, op=cv2.MORPH_TOPHAT, kernel=kernel, iterations=2)
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(img_erode)
    plt.show()


# 黑帽 闭运算-原图  突出原图中暗的地方
if control == 7:
    img = cv2.imread("../haizei.jpg")
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(9, 9))
    img_erode = cv2.morphologyEx(img, op=cv2.MORPH_BLACKHAT, kernel=kernel, iterations=3)
    plt.figure(figsize=(12, 6))
    plt.subplot(121)
    plt.imshow(img)
    plt.subplot(122)
    plt.imshow(img_erode)
    plt.show()

图像分割 分割原则 分割的原则就是使划分后的子图在内部保持相似度最大,而子图之间的相似度保持最小。
固定阈值分割

if control == 1:
    img = cv2.imread("../2bin.jpg", flags=0)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    # 大于thresh 置为maxval; 小于thresh,置为0
    ret1, th1 = cv2.threshold(img, thresh=100, maxval=255, type=cv2.THRESH_BINARY)
    # 小于thresh 置为maxval; 大于thresh,置为0
    ret2, th2 = cv2.threshold(img, thresh=127, maxval=255, type=cv2.THRESH_BINARY_INV)
    # 大于thresh 置为thresh, 小于thresh,不处理
    ret3, th3 = cv2.threshold(img, thresh=127, maxval=255, type=cv2.THRESH_TRUNC)
    # 大于thresh 不做处理, 小于thresh,置为0
    ret4, th4 = cv2.threshold(img, thresh=127, maxval=255, type=cv2.THRESH_TOZERO)
    ret5, th5 = cv2.threshold(img, thresh=127, maxval=255, type=cv2.THRESH_TOZERO_INV)
    ths = [img, th1, th2, th3, th4, th5]
    plt.figure(figsize=(12, 6))
    for i in range(6):
        plt.subplot(2, 3, i+1)
        plt.imshow(ths[i])
    plt.show()

自适应阈值分割

自适应阈值法会每次取出图像的一小部分计算阈值,这样不同区域的阈值就不尽相同,适用于明暗分布不均的图片。

# 自适应阈值分割
if control == 2:
    img = cv2.imread("../black_white.jpg", flags=0)
    print(img.shape)
    # 分成blockSize的小区域,其中使用均值来计算阈值
    th1 = cv2.adaptiveThreshold(
        img, maxValue=255, adaptiveMethod=cv2.ADAPTIVE_THRESH_MEAN_C,
        thresholdType=cv2.THRESH_BINARY, blockSize=11, C=4)
    # 分成blockSize的小区域,其中使用高斯加权来计算阈值
    th2 = cv2.adaptiveThreshold(
        img, maxValue=255, adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        thresholdType=cv2.THRESH_BINARY, blockSize=17, C=6)
    ths = [img, th1, th2]
    plt.figure(figsize=(12, 6))
    for i in range(3):
        plt.subplot(1, 3, i+1)
        plt.imshow(ths[i], cmap=plt.get_cmap("gray"))
    plt.show()

迭代法阈值分割
  1. 求出图像的最大灰度和最小灰度值,分别记为Zmax和Zmin,令初始阈值T0 = 1/2(Zmax + Zmin)。
  2. 根据阈值Tk,将图像分为前景和背景,分别求出两者的平均灰度值ZO和ZB。
  3. 求出新阈值Tk+1 = 1/2(ZO + ZB)。
  4. 若TK = TK + 1,则得到最终阈值,否则继续迭代2。
  5. 使用最后的阈值。
大津阈值分割法

# 大津法阈值分割
if control == 3:
    img = cv2.imread("../dark_girl.jpg", flags=0)
    print(type(img))
    # 阈值法
    _, th1 = cv2.threshold(img, 100, maxval=255, type=cv2.THRESH_BINARY)
    # 大津法
    _, th2 = cv2.threshold(img, 0, maxval=255, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    # 高斯滤波 + 大津法(常用套路)
    blur = cv2.GaussianBlur(img, ksize=(7, 7), sigmaX=0)
    _, th3 = cv2.threshold(blur, 0, maxval=255, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    plt.figure(figsize=(12, 6))
    plt.subplot(221)
    plt.title("origin")
    plt.imshow(img, cmap=plt.get_cmap("gray"))
    plt.subplot(222)
    plt.title("thresh")
    plt.imshow(th1, cmap=plt.get_cmap("gray"))
    plt.subplot(223)
    plt.title("otsu")
    plt.imshow(th2, cmap=plt.get_cmap("gray"))
    plt.subplot(224)
    plt.title("gussian + otsu")
    plt.imshow(th3, cmap=plt.get_cmap("gray"))
    plt.show()
边缘检测
  1. 梯度:梯度是一个向量,梯度方向指向函数变化最快的方向,梯度的大小是最大的变化率。
  2. 边缘:边缘就是梯度变化很快的点的集合。
  3. 梯度算子:梯度算子是一阶导数的算子,是水平梯度模板和竖直梯度模板的组合,也有对角线的方向。
  4. 常见的梯度算子:
  • Roberts交叉算子

优点:边缘定位较为准确,适用于边缘明显且噪声少的图像。

缺点:没有描述水平和竖直方向的灰度变化,只关注了对角线方向,容易造成边缘的遗漏;鲁棒性差,因为点本身参与梯度的计算,不能有效抑制噪声的干扰。

  • Prewitt算子

Prewitt算子引入了类似局部平均的运算,对噪声更有平滑作用,可以抑制噪声。

  • Sobel算子

  • canny边缘检测算法

if control == 2:
    img = cv2.imread("../road.png", 0)
    img_mask = cv2.imread("../road_mask.png", 0)
    img_mask = 1 - img_mask
    #print(img_mask)
    v1 = cv2.Canny(img, threshold1=60, threshold2=150, edges=(5, 5))
    v1_ = v1 * img_mask
    v2 = cv2.Canny(img, threshold1=50, threshold2=100, edges=(5, 5))
    v2_ = v2 * img_mask
    v_mask = cv2.Canny(img_mask, threshold1=0, threshold2=1, edges=(3, 3))
    plt.figure(figsize=(9, 18))
    plt.subplot(321)
    plt.title("origin")
    plt.imshow(img, cmap=plt.get_cmap("gray"))
    plt.subplot(322)
    plt.title("mask")
    plt.imshow(img_mask, cmap=plt.get_cmap("gray"))
    plt.subplot(323)
    plt.title("v1")
    plt.imshow(v1, cmap=plt.get_cmap("gray"))
    plt.subplot(324)
    plt.title("v2")
    plt.imshow(v2, cmap=plt.get_cmap("gray"))
    plt.subplot(325)
    plt.title("v1_")
    plt.imshow(v1_, cmap=plt.get_cmap("gray"))
    plt.subplot(326)
    plt.title("v_mask")
    plt.imshow(v_mask, cmap=plt.get_cmap("gray"))
    plt.show()

连通区域

连通区域:一般指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域。连通区域分析是将图像中各自联通区域找到并且标记。

Two-pass算法:

区域生长

区域生长:是一种串行区域分割的方法。从某个像素出发,按照一定的准则,逐步加入邻近像素。当满足一定的条件的时候,区域停止生长。

分水岭算法 原理:任意的灰度图像。高亮度的是山峰,低亮度的是山谷。



版权声明:本文为weixin_36714575原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。