一、理论基础
①奇异值分解(SVD)原理与在降维中的应用
转自:奇异值分解(SVD)原理与在降维中的应用 - 刘建平Pinard - 博客园
奇异值分解
奇异值分解(SVD)是在机器学习领域广泛应用的算法,它不光可以用于降维算法中的特征分解,还可以用于推荐系统,以及自然语言处理等领域。是很多机器学习算法的基石。
SVD的定义
SVD也是对矩阵进行分解,但是和特征分解不同,SVD并不要求要分解的矩阵为方阵。假设我们的矩阵A是一个m×n的矩阵,那么我们定义矩阵A的SVD为:
A=UΣVT
其中U是一个m×m的矩阵,Σ是一个m×n的矩阵,除了主对角线上的元素以外全为0,主对角线上的每个元素都称为奇异值,V是一个n×n的矩阵。U和V都是酉矩阵,即满足UTU=I,VTV=I。下图可以很形象的看出上面SVD的定义:

那么我们如何求出SVD分解后的U,Σ,V这三个矩阵呢?
如果我们将A的转置和A做矩阵乘法,那么会得到n×n的一个方阵ATA。既然ATA是方阵,那么我们就可以进行特征分解,得到的特征值和特征向量满足下式:
(ATA)vi=λivi
这样我们就可以得到矩阵ATA的n个特征值和对应的n个特征向量v了。将ATA的所有特征向量张成一个n×n的矩阵V,就是我们SVD公式里面的V矩阵了。一般我们将V中的每个特征向量叫做A的右奇异向量。如果我们将A和A的转置做矩阵乘法,那么会得到m×m的一个方阵AAT。既然AAT是方阵,那么我们就可以进行特征分解,得到的特征值和特征向量满足下式:
(AAT)ui=λiui
这样我们就可以得到矩阵AAT的m个特征值和对应的m个特征向量u了。将AAT的所有特征向量张成一个m×m的矩阵U,就是我们SVD公式里面的U矩阵了。一般我们将U中的每个特征向量叫做A的左奇异向量。U和V我们都求出来了,现在就剩下奇异值矩阵Σ没有求出了。由于Σ除了对角线上是奇异值其他位置都是0,那我们只需要求出每个奇异值σ就可以了。
我们注意到:
A=UΣVT⇒AV=UΣVTV⇒AV=UΣ⇒Avi=σiui⇒σi=Avi/ui
这样我们可以求出我们的每个奇异值,进而求出奇异值矩阵Σ。上面还有一个问题没有讲,就是我们说ATA的特征向量组成的就是我们SVD中的V矩阵,而AAT的特征向量组成的就是我们SVD中的U矩阵,这有什么根据吗?这个其实很容易证明,我们以V矩阵的证明为例。
A=UΣVT⇒AT=VΣTUT⇒ATA=VΣTUTUΣVT=VΣ2VT
上式证明使用了:UTU=I,ΣTΣ=Σ2。
可以看出ATA的特征向量组成的的确就是我们SVD中的V矩阵。类似的方法可以得到AAT的特征向量组成的就是我们SVD中的U矩阵。进一步我们还可以看出我们的特征值矩阵等于奇异值矩阵的平方,也就是说特征值和奇异值满足如下关系:
σi=λi−−√
这样也就是说,我们可以不用σi=Avi/ui来计算奇异值,也可以通过求出ATA的特征值取平方根来求奇异值。
②图像的开闭运算(腐蚀-膨胀)
摘自:图像的腐蚀(erode)和膨胀(dilate) 开运算以及闭运算 - MATLAB论坛 - 数学建模社区-数学中国
腐蚀和膨胀是数学形态学上的名词,如果用于图像处理上则就称为图像二值形态学。形态学主要是为了获取物体的拓扑和结构信息,通过物体和结构元素相互作用的某些运算,得到物体更本质的形态。当形态学运用到图像处理中,它的主要作用是利用形态学的基本运算,如腐蚀和膨胀运算,对图像进行观察和处理,从而达到改善图像质量的目地;描述和定义图像的各种几何参数和特征
腐蚀的具体操作:用一个结构元素(也可以说成操作数矩阵)一般为3×3大小的,也可以看成一个卷积模板,它们的区别就在于卷积模板是以算术运算为基础的,而结构元素是以集合运算为基础的,扫描图像中的每一个像素(关于图像边缘的处理见下面详细分析),用操作数矩阵扫描图像中的每一个像素,操作数矩阵中每一个像素与覆盖的像素做“与”操作,如果全部为1,则图像中的该像素为1,反之为0,而膨胀操作正好相反,全部为0时,则图像素中的该像素为0,反之为1。腐蚀的作用是消除物体的边界点,使目标缩小,这个根据操作的过程可以显然的想到,物体的边界处像素值肯定是有0和1,腐蚀操作后这些紧邻着为1的像素点都会变成0,所以腐蚀操作会消除那些小的且无意义的物体,使边界向内部收缩的过程。相反,膨胀的作用当然是使目标增大,填充物体内细小的空洞,并且平滑物体的边界,边界向外部扩张的作用。
开运算是先腐蚀后膨胀的过程,可以消除图像上细小的噪声,并平滑物体的边界;闭运算是先膨胀后腐蚀的过程,可以填充物体内细小的空洞,并平滑物体边界通常,由于噪声的影响,图象在阈值化后所得到边界往往是很不平滑的,物体区域具有一些噪声孔,背景区域上散布着一些小的噪声物体。连续的开和闭运算可以有效地改善这种情况。有时需要经过多次腐蚀之后再加上相同次数的膨胀,才可以产生比较好的效果。腐蚀操作会去掉物体的边缘点,细小物体所有的点都会被认为是边缘点,因此会整个被删去。再做膨胀时,留下来的大物体会变回原来的大小,而被删除的小物体则永远消失了。膨胀操作会使物体的边界向外扩张,如果物体内部存在小空洞的话,经过膨胀操作这些洞将被补上,因而不再是边界了。再进行腐蚀操作时,外部边界将变回原来的样子,而这些内部空洞则永远消失了。
二、采用奇异值分解来对一张图片进行特征值提取处理
代码如下
import numpy as np
import os
from PIL import Image
import matplotlib.pyplot as plt
import matplotlib as mpl
from pprint import pprint
def restore1(sigma, u, v, K): # 奇异值、左特征向量、右特征向量
m = len(u)
n = len(v[0])
a = np.zeros((m, n))
for k in range(K):
uk = u[:, k].reshape(m, 1)
vk = v[k].reshape(1, n)
a += sigma[k] * np.dot(uk, vk)
a[a < 0] = 0
a[a > 255] = 255
# a = a.clip(0, 255)
return np.rint(a).astype('uint8')
def restore2(sigma, u, v, K): # 奇异值、左特征向量、右特征向量
m = len(u)
n = len(v[0])
a = np.zeros((m, n))
for k in range(K+1):
for i in range(m):
a[i] += sigma[k] * u[i][k] * v[k]
a[a < 0] = 0
a[a > 255] = 255
return np.rint(a).astype('uint8')
if __name__ == "__main__":
A = Image.open("./picwork.png", 'r')
print(A)
output_path = r'./SVD_Output'
if not os.path.exists(output_path):
os.mkdir(output_path)
a = np.array(A)
print(a.shape)
K = 50
u_r, sigma_r, v_r = np.linalg.svd(a[:, :, 0])
u_g, sigma_g, v_g = np.linalg.svd(a[:, :, 1])
u_b, sigma_b, v_b = np.linalg.svd(a[:, :, 2])
plt.figure(figsize=(11, 9), facecolor='w')
mpl.rcParams['font.sans-serif'] = ['simHei']
mpl.rcParams['axes.unicode_minus'] = False
for k in range(1, K+1):
print(k)
R = restore1(sigma_r, u_r, v_r, k)
G = restore1(sigma_g, u_g, v_g, k)
B = restore1(sigma_b, u_b, v_b, k)
I = np.stack((R, G, B), axis=2)
Image.fromarray(I).save('%s\\svd_%d.png' % (output_path, k))
if k <= 12:
plt.subplot(3, 4, k)
plt.imshow(I)
plt.axis('off')
plt.title('奇异值个数:%d' % k)
plt.suptitle('SVD与图像分解', fontsize=20)
plt.tight_layout(0.3, rect=(0, 0, 1, 0.92))
# plt.subplots_adjust(top=0.9)
plt.show()
执行程序后会在生成的SVD_Output文件夹中生成经过不同奇异值降维过的图片,效果如下:

文件夹中生成的图片:

三、图像的开闭运算(腐蚀-膨胀)
腐蚀和膨胀
图片如下:


代码(引用:图像形态学变换 膨胀和腐蚀 开闭运算 边缘检测 opencv实现 - 我坚信阳光灿烂 - 博客园)
# Writer : wojianxinygcl@163.com
# Date : 2020.3.22
import cv2 as cv
import numpy as np
image = cv.imread("./coin2.jpg")
image = cv.cvtColor(image,cv.COLOR_RGB2GRAY)
# 得到二值图像 定义阈值为127
#ret,thresh1 = cv.threshold(image,127,255,cv.THRESH_BINARY)
# 得到二值图像 自动计算合适的阈值
ret,thresh1 = cv.threshold(image,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
Black_and_White = thresh1.copy()
# 得到腐蚀图像,3*3为卷积核大小
erode_result = cv.erode(thresh1,(3,3))
# 得到膨胀图像,3*3为卷积核大小
dilate_result = cv.dilate(thresh1,(3,3))
cv.imshow('Black_and_White',Black_and_White)
cv.imshow("erode_result", erode_result)
cv.imshow("dilate_result",dilate_result)
cv.waitKey(0)
cv.destroyAllWindows()
输出的结果:


import cv2
'1. 初始操作'
src = cv2.imread("./coin.png", 17)
img = src.copy()
'2. 灰度化——cvtColor()参数含义:1)原图 2)目标颜色空间,例如 RGB->HSV'
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)
'3. 阈值分割(二值化)——threshold()参数含义:1)8位图像(灰度图) 2)比较阈值 3)输出值 4)分割属性'
'返回值函数: ret=比较阈值 thresh=二值化图像(矩阵数组)'
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
print(ret)
'4. 完成显示'
cv2.imshow("src Image", src)
cv2.imshow("thresh Image", thresh)
cv2.waitKey()

import cv2
'''
开操作与闭操作
开操作 = 腐蚀 + 膨胀 消除部分高亮区域(二值化中的白色区域)
闭操作 = 膨胀 + 腐蚀 消除高亮区域的内部黑洞(二值化中的黑色区域)
'''
import cv2
'1. 初始操作'
src = cv2.imread("./coin.png", 0)
img = src.copy()
'2. 二值化——threshold'
ret, thresh = cv2.threshold(src, 127, 255, cv2.THRESH_BINARY)
cv2.imshow('thresh', thresh)
'3. 获取形态学变换结构元——RECT=矩形'
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
'4. 形态学变换函数——morphologyEx()参数含义:1)原图 2)变换属性(开/关操作、礼帽、梯度等) 3)结构元 '
open = cv2.morphologyEx(src, cv2.MORPH_OPEN, kernel, iterations=2)
# op_erode = cv2.erode(src, kernel, iterations=1)
# open = cv2.dilate(op_erode, kernel, iterations=1)
'5. 完成显示'
cv2.imshow("Src Image ", img)
cv2.imshow("MORPH_OPEN Image", open)
cv2.waitKey(0)
四、OpenCV检测图像中的条形码
代码
import cv2
import numpy as np
import imutils
from pyzbar import pyzbar
def stackImages(scale, imgArray):
"""
将多张图像压入同一个窗口显示
:param scale:float类型,输出图像显示百分比,控制缩放比例,0.5=图像分辨率缩小一半
:param imgArray:元组嵌套列表,需要排列的图像矩阵
:return:输出图像
"""
rows = len(imgArray)
cols = len(imgArray[0])
rowsAvailable = isinstance(imgArray[0], list)
width = imgArray[0][0].shape[1]
height = imgArray[0][0].shape[0]
if rowsAvailable:
for x in range(0, rows):
for y in range(0, cols):
if imgArray[x][y].shape[:2] == imgArray[0][0].shape[:2]:
imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale)
else:
imgArray[x][y] = cv2.resize(imgArray[x][y], (imgArray[0][0].shape[1], imgArray[0][0].shape[0]),
None, scale, scale)
if len(imgArray[x][y].shape) == 2: imgArray[x][y] = cv2.cvtColor(imgArray[x][y], cv2.COLOR_GRAY2BGR)
imageBlank = np.zeros((height, width, 3), np.uint8)
hor = [imageBlank] * rows
hor_con = [imageBlank] * rows
for x in range(0, rows):
hor[x] = np.hstack(imgArray[x])
ver = np.vstack(hor)
else:
for x in range(0, rows):
if imgArray[x].shape[:2] == imgArray[0].shape[:2]:
imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale)
else:
imgArray[x] = cv2.resize(imgArray[x], (imgArray[0].shape[1], imgArray[0].shape[0]), None, scale, scale)
if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR)
hor = np.hstack(imgArray)
ver = hor
return ver
#读取图片
src = cv2.imread("./Image.png")
img = src.copy()
#灰度
img_1 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#高斯滤波
img_2 = cv2.GaussianBlur(img_1, (5, 5), 1)
#Sobel算子
sobel_x = cv2.Sobel(img_2, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(img_2, cv2.CV_64F, 0, 1, ksize=3)
sobel_x = cv2.convertScaleAbs(sobel_x)
sobel_y = cv2.convertScaleAbs(sobel_y)
img_3 = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
#均值方波
img_4 = cv2.blur(img_3, (5, 5))
#二值化
ret, img_5 = cv2.threshold(img_4, 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
#闭运算
kernel = np.ones((100, 100), int)
img_6 = cv2.morphologyEx(img_5, cv2.MORPH_CLOSE, kernel)
#开运算
kernel = np.ones((200, 200), int)
img_7 = cv2.morphologyEx(img_6, cv2.MORPH_OPEN, kernel)
#绘制条形码区域
contours = cv2.findContours(img_7, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)
c = sorted(contours, key = cv2.contourArea, reverse = True)[0]
rect = cv2.minAreaRect(c)
box = cv2.cv.BoxPoints(rect) if imutils.is_cv2() else cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img, [box], -1, (0,255,0), 20)
#显示图片信息
cv2.putText(img, "results", (200, 200), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), 3)
cv2.putText(img_1, "gray", (200, 200), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 3)
cv2.putText(img_2, "GaussianBlur",(200, 200), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 3)
cv2.putText(img_3, "Sobel", (200, 200), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), 3)
cv2.putText(img_4, "blur", (200, 200), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), 3)
cv2.putText(img_5, "threshold", (200, 200), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), 3)
cv2.putText(img_6, "close", (200, 200), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 3)
cv2.putText(img_7, "open", (200, 200), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 3)
#输出条形码
barcodes = pyzbar.decode(src)
for barcode in barcodes:
barcodeData = barcode.data.decode("utf-8")
cv2.putText(img, barcodeData, (200, 600), cv2.FONT_HERSHEY_SIMPLEX, 5.0, (0, 255, 0), 30)
#显示所有图片
imgStack = stackImages(0.2, ([img_1, img_2],[img_3,img_4],[img_5,img_6],[img_7,img]))
cv2.imshow("imgStack", imgStack)
cv2.waitKey(0)
运行效果

五、参考
奇异值分解(SVD)原理与在降维中的应用 - 刘建平Pinard - 博客园
本文介绍了奇异值分解(SVD)的基本原理及其在图像降维中的应用,同时探讨了图像处理中的开闭运算,包括腐蚀和膨胀,以及在消除噪声和平滑边界方面的效果。此外,还展示了使用OpenCV进行条形码检测的实际代码和运行结果。


1万+

被折叠的 条评论
为什么被折叠?



