Machine Learning——CV系列(一)——Python+OpenCV核心操作(4)

15 篇文章 0 订阅
13 篇文章 3 订阅

五、图像金字塔

5.1 高斯金字塔(低通)

高斯金字塔有两种形式:上采样和下采样。上采样是图片逐渐变大的过程;下采样是图片逐渐变小的过程。两种方式都会使图片越来越模糊。

import cv2

img = cv2.imread(r"13.jpg")
for i in range(3):
    cv2.imshow(f"img{i}",img)
    # img = cv2.pyrDown(img)# 12.jpg
    img = cv2.pyrUp(img)# 13.jpg

cv2.waitKey(0)

上采样
在这里插入图片描述
下采样
在这里插入图片描述

5.2 拉普拉斯金字塔(高通)

拉普拉斯金字塔需要用到高斯金字塔,公式如下:在这里插入图片描述
在这里插入图片描述
高频信号=原图-经过高斯模糊的下采样的上采样(高频信号被删除)

import cv2

img = cv2.imread(r"12.jpg")# 获得一张原图=>图0
img_down = cv2.pyrDown(img)# 首先用高斯下采样一次=>图1
img_up = cv2.pyrUp(img_down) # 然后将图1用高斯上采样一次=>图2
img_new = cv2.subtract(img, img_up)# 最后用原图0-图2得到结果
#为了更容易看清楚,做了个提高对比度的操作
img_new = cv2.convertScaleAbs(img_new, alpha=5, beta=0)
cv2.imshow("img_LP", img_new)
cv2.waitKey(0)

在这里插入图片描述

5.3 图像金字塔用途

可以将两种图片以一种良好过渡的感觉拼接起来。
应用:AI换脸。用到了金字塔。
无缝融合。

六、模板匹配

6.1 无缝融合(苹果与橘子融合的项目)

过程:
在这里插入图片描述

import cv2
import numpy as np

A = cv2.imread('21.jpg')
B = cv2.imread('22.jpg')

# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in range(6):
    G = cv2.pyrDown(G)
    gpA.append(G)

# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in range(6):
    G = cv2.pyrDown(G)
    gpB.append(G)

# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in range(5, 0, -1):
    GE = cv2.pyrUp(gpA[i])
    L = cv2.subtract(gpA[i - 1], GE)
    lpA.append(L)



# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in range(5, 0, -1):
    GE = cv2.pyrUp(gpB[i])
    L = cv2.subtract(gpB[i - 1], GE)
    lpB.append(L)

# Now add left and right halves of images in each level
LS = []
for i, (la, lb) in enumerate(zip(lpA, lpB)):
    rows, cols, dpt = la.shape
    ls = np.hstack((la[:, 0:cols // 2], lb[:, cols // 2:]))
    LS.append(ls)

# now reconstruct
ls_ = LS[0]
for i in range(1, 6):
    ls_ = cv2.pyrUp(ls_)
    ls_ = cv2.add(ls_, LS[i])
    # cv2.imshow(f"xxx{i}", ls_)

# image with direct connecting each half
real = np.hstack((A[:, :cols // 2], B[:, cols // 2:]))

cv2.imshow('Pyramid_blending.jpg', ls_)
cv2.imshow('Direct_blending.jpg', real)

cv2.waitKey(0)

在这里插入图片描述

七、图像直方图

直方图:衡量一张图片像素的分布规律。
意义:对图像进行"矫正"。

import cv2
import matplotlib.pyplot as plt

img = cv2.imread('1.jpg')
# img[...,0]=0
# img[...,1]=0
cv2.imshow("...",img)

img_B = cv2.calcHist([img], [0], None, [256], [0, 256])
r'''
img是原图,channels是某一个通道,histSize是直方图有多少个区间,ranges是区间开始和结束
上面四个值都需要[]括起来,变成一个list。(本例histSize和ranges为[256], [0, 256])
mask是一个大小和image一样的np数组,其中把需要处理的部分指定为1,不需要处理的部分指定为0,
一般设置为None,表示处理整幅图像
'''
plt.plot(img_B, label='B', color='b')

img_G = cv2.calcHist([img], [1], None, [256], [0, 256])
plt.plot(img_G, label='G', color='g')
#
img_R = cv2.calcHist([img], [2], None, [256], [0, 256])
plt.plot(img_R, label='R', color='r')

plt.show()

在这里插入图片描述

7.1 直方图均衡化

可以通过方法让像素分布更加"分散"。对"整个图片"均衡。(去模糊

import cv2
import matplotlib.pyplot as plt

img = cv2.imread('7.jpg', 0)
cv2.imshow("src", img)

his = cv2.calcHist([img], [0], None, [255], [0, 255])
plt.plot(his, label='his', color='r')
# plt.show()

dst = cv2.equalizeHist(img)
cv2.imshow("dst", dst)

cv2.imwrite("15.jpg", dst)

his = cv2.calcHist([dst], [0], None, [255], [0, 255])
plt.plot(his, label='his', color='b')
plt.show()

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

7.2 自适应均衡化

上面是对整个图片均衡,更好的方法是类似卷积一样,逐步逐步的均衡。

import cv2

img = cv2.imread('8.jpg', 0)
cv2.imshow("src", img)

dst1 = cv2.equalizeHist(img)# 可以通过调整像素分布修改图像效果
cv2.imshow("dst1", dst1)

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
r'''
clipLimit:颜色对比度的阈值
titleGridSize:进行像素均衡化的网格大小[类比卷积核]
'''
dst2 = clahe.apply(img)# 进行自适应直方图均衡化
cv2.imshow("dst2", dst2)

cv2.waitKey(0)

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

7.3 2D直方图

在上面"直方图均衡化"中,我们是把彩色图片转换为灰度图片,可以看做是"1D直方图"。而2D直方图是取彩色图片两个通道来作为横纵坐标进行展示,很明显RGB模式并不适合,而HSV空间就比较合适了(颜色是连续的)。我们主要分析H、S通道,明度就先抛弃了。

7.4 直方图反向投影

反向投影(Back Projection):是直方图运算的逆过程。直方图运算是统计每个灰度值对应的像素个数,而反向投影则是将像素个数回送到该像素个数对应灰度区间的像素位置。

用一个简单例子来解释反向投影,假设有这么一个4×4的灰度图像:
在这里插入图片描述
将灰度分为四个区间[0-2]=1;[3-5]=2;[6-7]=3;[8-10]=4,可以统计到灰度图像在这四个区间上的灰度直方图为[4,4,6,2],就是cv2.calcHist()的结果。然后我们将这个直方图的区间上的结果[4,4,6,2]对应到图中[0-2];[3-5];[6-7];[8-10]上,即img[0,0]=1,1属于[0-2]区间,1就替换成4…以此类推,这就是直方图反投影,其本质是把直方图的结果"映射"到图像上形成一个"图像掩码"的东西,可以去提取对应特征值。
在这里插入图片描述

7.4.1 反向投影能做什么

前面讲过,反向投影是基于直方图的逆运算,而直方图则反应了图像的色彩(亮度)特征,当两幅相似的图像仅发生位置的变化而色彩(光线)几乎不变时,对应的两幅直方图相似度会很高。但直方图只是得到了特征,反向投影则是将特征"反应"到图像上,对于物体特征识别和分割有着很大的作用。

进行反向投影的一般步骤:

  • 获取直方图的源,比如我们要对手进行反向投影,那么直方图的源就是肤色的ROI
  • 获取需要反向投影的图像
  • 计算直方图,通常是在HSV模型中计算,毕竟反向投影的一大工作就是区分颜色
  • 反向投影
  • 显示
import cv2
import numpy as np

roi = cv2.imread('10.jpg')# 这是在原图截取部分的图片
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
target = cv2.imread('9.jpg')# 这是原图
hsvt = cv2.cvtColor(target, cv2.COLOR_BGR2HSV)# 转成hsv颜色空间

roihist = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])## 2D直方图操作

cv2.normalize(roihist, roihist, 0, 255, cv2.NORM_MINMAX)
'''
归一化:
src=roihist:原始图像
dst=roihist:结果图像
alpha,beta=0,255:映射到结果图像中的最小值和最大值
norm_type=归一化的类型,可以取以下值:
-----------------------------------------------------------------------------------
cv2.NORM_MINMAX :数组的数值缩放到一个指定的范围,线性归一化,一般较常用
cv2.NORM_INF    :归一化数组的C-范数(绝对值的最大值)
cv2.NORM_L1     :归一化数组的L1-范数(绝对值的和)
cv2.NORM_L2     :归一化数组的(欧几里德)L2-范数
-----------------------------------------------------------------------------------
本例用的是cv2.NORM_MINMAX:归一化类型,对数组的所有值进行转化,使他们线性映射到最小值和最大值之间。归一化后数字就到[0,255]之间了,便于直方图显示。
'''
dst = cv2.calcBackProject([hsvt], [0, 1], roihist, [0, 180, 0, 256], 1)
r'''
images:输入的图像集合,需要有相同的尺寸和深度,其中深度需为CV_8U、CV_16U或CV_32F
channels:要统计哪个通道的像素,彩色图像的话就有3个通道
hist:反向投影的计算源:直方图
ranges:统计量的取值范围,注意不是纵轴的取值范围,而是横轴的取值范围比如统计量是像素,那么通常就是像素值的取值范围
scale:缩放系数,默认值为1
---------------------------------------------------------------------------
返回的是一个二值化的图,即所有类似roi的部分都变成255了,其它地方都是0(黑色)
'''
# 然后再去进行其它操作...
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
dst = cv2.filter2D(dst, -1, disc)

ret, thresh = cv2.threshold(dst, 50, 255, 0)

thresh = cv2.merge((thresh, thresh, thresh))

res = cv2.bitwise_and(target, thresh)
res = np.hstack((target, thresh, res))

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

在这里插入图片描述

八、霍夫变换

图像处理中,霍夫变换用来检测任意能够用数学公式表达的形状,即使这个形状被破坏或者有点扭曲

霍夫变换是将xy坐标系转换到类似极坐标的坐标系:模长ρ和角度θ,可以用来检测"任意能用数学公式表达的形状"。在图上取多个点,将其投影到霍夫变换中,这多个点如果有公共交集,就找到了该形状的极坐标参数(即得到了模长和角度)。然后再转换为xy坐标系的一组点即轮廓。霍夫变换经常用于检测圆形和直线。

流程:

①轮廓算法检测出轮廓
②投射到Hough空间进行形状检测

原理:

(opencv里是用参数方程代替空间坐标系,ρ/θ)

空间坐标系的线对应霍夫坐标系的点
空间坐标系的点对应霍夫坐标系的线
如果空间坐标系有几点共线,在霍夫坐标系就有同一个交点
在这里插入图片描述

8.1 直线检测

import cv2
import numpy as np

image = cv2.imread("27.jpg")# 得到需要检测的原图

image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 得到灰度图像

edges = cv2.Canny(image_gray, 100, 150)# Canny描出轮廓

lines = cv2.HoughLines(edges, 1, np.pi / 180, 100)
r'''
这是找出图中的直线。关键参数四个: image, rho, theta, threshold
------------------------------------------------------------------------------
image=edges,这是传入的二值图
rho=1,指ρ的精确度(最小步长为1像素)
theta=π/180,指θ的精确度(最小角度为π/180)
threshold=300,是个经验值,指可以被认为是一个线条的最小计数值。
由于计数值的多少取决于线上的点数,所以这代表了可以被识别为线的最小长度。
------------------------------------------------------------------------------
函数输出的是(float,float)形式的ndarray,打印结果其shape应该是(N,1,2),N代表直线条数,
1代表一个直线对象(ρ,θ),2表示检测到的线(ρ,θ)中浮点值的参数。
'''
for line in lines:# 这里是循环所有找到的直线
    rho, theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    # 上面四步是将极坐标转回到xy坐标系
    x1 = int(x0 + 1000 * (-b))  # 直线起点横坐标
    y1 = int(y0 + 1000 * (a))  # 直线起点纵坐标
    x2 = int(x0 - 1000 * (-b))  # 直线终点横坐标
    y2 = int(y0 - 1000 * (a))  # 直线终点纵坐标
    cv2.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2)## 描点划线

cv2.imshow("image_lines", image)# 展示结果
cv2.waitKey(0)

在这里插入图片描述

8.2 圆检测

import cv2
import numpy as np

image = cv2.imread("29.jpg")
dst = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)
circle = cv2.HoughCircles(dst, cv2.HOUGH_GRADIENT, 1, 80, param1=40, param2=20, minRadius=20, maxRadius=300)
r'''
这是在找图片中的圆,参数如下:
------------------------------------------------------------------------------
edges是灰度的轮廓,
method:使用检测方法。目前,唯一实现的方法是 CV_HOUGH_GRADIENT。
dp:累加器分辨率与图像分辨率的反比。
    如果 dp = 1,则累加器具有与输入图像相同的分辨率;
    如果 dp = 2,则累加器的宽度和高度都是一半。
minDist:检测到的圆的中心之间的最小距离。如果参数太小,除了真正的参数外,
	    可能会错误地检测到多个邻居圈;如果太大,可能会错过一些圈子。
param1:第一个方法特定的参数。在CV_HOUGH_GRADIENT表示传入Canny边缘检测的阈值
param2:第二种方法参数。在CV_HOUGH_GRADIENT的情况下,它是检测阶段的圆心的累加器阈值。
	   越小,可能会检测到越多的虚假圈子。首先返回对应于较大累加器值的圈子。
minRadius:最小圆半径。
maxRadius:最大圆半径。
返回的shape应该是(1,N,3):第一个不知道干啥的,第二个N表示找到的圆的个数,
第三个是返回每个圆的参数,分别为(x,y,半径)所以是3。
------------------------------------------------------------------------------
'''
if not circle is None:# 循环所有找到的圆
    circle = np.uint16(np.around(circle))
    for i in circle[0, :]:# 画圆
        cv2.circle(image, (i[0], i[1]), i[2], (0, 0, 255), 2)

cv2.imshow("circle", image)# 展示结果
cv2.waitKey(0)

在这里插入图片描述

九、分水岭算法

分水岭算法是一种经典且实用的 切割算法
分水岭算法中有一个核心思想:距离变换。

9.1 距离变换

距离变换是指把某点到某个特定区域,一般是二值化图的黑色部分,因为黑色=0,可以代表背景。然后我们定义某一个图像,或者某一个不为零的像素,其到背景0的最短距离的数值替换成像素值,那么整个图片中,越远离背景的地方就越"亮",越靠近背景的地方就越"黑"。再配合一个阈值操作,就可以找到每一个需要切割的图像的中心点了,因为中心点肯定是最亮的。

但是如果要计算最短距离,就得算出所有距离然后取min,这样计算量十分庞大。科学家想出了用腐蚀的方法来计算距离:从图形边缘开始腐蚀,每腐蚀一次,被腐蚀的点的距离就是"+1",直到把整个图形腐蚀掉,这样一遍操作,里面所有的点的距离都得到了。

我个人观点:距离变换的思想和上面图像直方图中直方图反向投影是类似的,反向投影是把该像素在整体像素分布中所占的"权重"代替原像素值;而距离变换是把该像素在黑色背景中的"海拔高度"来代替原像素值。用不同的角度去寻找图像的特征,这应该是我们可以学习的一种全新思路。

9.2 分水岭算法原理

**分水岭算法,顾名思义,就是用"水"来分割"山岭"。这里的山就是距离变换后的二值化图,水就是用来切割并标记的工具。**我简单说一下我的理解:首先背景是黑色的,高度为0,不同的边缘有不同的高度。然后开始灌水,一开始水都是在最底层,互相独立不连通,随着水高度越来越高,有些低洼的地方就被漫过,水就汇合了,每汇合一次,就在那个地方标一个"-1",这样直到淹没整个图,所有的轮廓都被标注出来了。由于一开始背景黑色就是0,所以只要找出小于0的就是轮廓了。

import numpy as np
import cv2

img = cv2.imread('30.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
# 上面3步是得到二值化后的图片thresh
# noise removal
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# 上面2步是对二值化图片进行开操作,为了去除周边背景的噪点
# sure background area
sure_bg = cv2.dilate(opening, kernel, iterations=3) # 膨胀操作,突出背景
# sure_bg = opening

dist_transform = cv2.distanceTransform(opening, 1, 5)
r'''
这一步叫距离变换,配合下面阈值操作,可以得到每个切割图像的大致中心点位置,
相当于进行了先验处理。
参数:
-----------------------------------------------------------------------
src=opening   :传入的二值图
distance_type :计算距离的公式,参看"轮廓"中=>3-4:凸包和凸性检测中cv2.fitLine()下的解释
mask_size     :尺寸。就是cv2.erode(img, kernel)中kernel的大小
     cv2.DIST_MASK_3 = 3
     cv2.DIST_MASK_5 = 5
     cv2.DIST_MASK_PRECISE = 0
-----------------------------------------------------------------------
'''
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)

# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

# Marker labelling
ret, markers1 = cv2.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers1 + 1
# Now, mark the region of unknown with zero
markers[unknown == 255] = 0

markers3 = cv2.watershed(img, markers)# 这就是分水岭算法
img[markers3 == -1] = [0, 0, 255]

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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

wa1tzy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值