Python实现:高斯滤波 均值滤波 中值滤波 Canny(边缘检测)PCA主成分分析 直方图规定化 Mean_Shift(文末附上整合这些函数的可视化界面并且已做打包处理)
1.高斯滤波(以下函数所有的图片路径为方便前来copy的同学,修改这里全设置为绝对路径,卷积核大小和其他参数按照自己需求改)
import cv2
import numpy as np
import math
SIZE = 3 # 卷积核大小(只能为奇数)
padding = SIZE // 2
sigma = 3
# 生成高斯卷积核(定卷积核中心坐标为(0,0))
GaussKernel = np.zeros((SIZE, SIZE))
for i in range(SIZE):
for j in range(SIZE):
xxAddyy = math.pow(i - padding, 2) + math.pow(j - padding, 2)
GaussKernel[i, j] = math.exp(-xxAddyy / (2 * math.pow(sigma, 2))) / 2 * math.pi * math.pow(sigma, 2)
sum = np.sum(GaussKernel)
GaussKernel = GaussKernel / sum # 归一化
""""
TmMask2 带有padding和img的模板
x,y为TmMask2映射到Temp的像素偏移量
"""
# 获取待模糊像素(x,y)附近Mask大小的像素区域
def GetMatrix(TmMask2, x, y):
Temp = np.zeros([SIZE, SIZE])
for X in range(SIZE):
for Y in range(SIZE):
Temp[X][Y] = TmMask2[x + X][y + Y]
return np.array(Temp) # 返回需要卷积的区域副本
"""*********************** 一.读取图像 ************************************"""
path_Gaussian =r'C:\Users\Administrator\Desktop\GUI2\fun\images\GaussianBlur.jpg'#输入图片的绝对路径
img = cv2.imread(path_Gaussian, 0)
img2 = cv2.imread(path_Gaussian, 0)
h, w = img.shape # 高度 宽度
"""*********************** 二.制作padding模板 ************************************"""
TmMask = np.zeros([h + padding * 2, w + padding * 2])
TmMask[padding:h + padding, padding:w + padding] = img # 把图像嵌入空白模板TmMask的padding内
"""*********************** 三.卷积并修改原灰度图的像素值 ************************************"""
# 对img每个像素循环卷积修改img图像数组中的值
for x in range(h):
for y in range(w):
# 卷积(数组相乘)并修改原图像素
img[x][y] = np.sum(GetMatrix(TmMask, x, y) * GaussKernel)
""" *********************** 四.高斯平滑后与原灰度图输出 ************************************"""
cv2.imshow("Gaussian", img)
cv2.imshow("SRC", img2)
cv2.waitKey()
左侧为原图右侧为高斯滤波后的图
2.均值滤波
import cv2
import numpy as np
path = r"C:\Users\Administrator\Desktop\GUI2\fun\images\LennaSaltNoise.jpg" # 图像路径
SIZE = 5#卷积核大小
padding = SIZE // 2 # 四个方向需要加的padding大小
def Middle(temp, x, y, SIZE):
sum = 0
for X in range(SIZE):
for Y in range(SIZE):
sum += temp[x + X][y + Y]
return round(sum / (SIZE * SIZE)) # 返回平均数(四舍五入)
"""*********************************************** START ***********************************************************"""
# img = Image.open("E:\\a2.jpg").convert('L')#读取图片并转为灰度图
# img = Image.open(path).convert('L')#读取图片并转为灰度图
img = cv2.imread(path, 0)
img2 = cv2.imread(path, 0)
h, w = img.shape # 行 列
# 生成一个带有padding的临时模板图像
TempArry = np.zeros([h + padding * 2, w + padding * 2]) # 生成一个和图像对应的加入padding的空白模板
TempArry[padding:h + padding, padding:w + padding] = img # 把图像嵌入空白模板的padding内
# 循环对像素进行逐像素中值滤波
for x in range(0, h):
for y in range(0, w):
img[x, y] = Middle(TempArry, x, y, SIZE) # 卷积后的中位数赋值给原灰度图像素
cv2.imshow("Mean", img)
cv2.imshow("SRC", img2)
cv2.waitKey()
左图为带有噪声的输入原图,右图为经过均值滤波后的图片
3.中值滤波
import cv2
import numpy as np
path = "E:\\LennaSaltNoise.jpg" # 图像路径
SIZE = 5 # 卷积核大小
padding = SIZE // 2 # 四个方向需要加的padding大小
def Middle(temp, x, y, SIZE):
# Mask = np.zeros([SIZE,SIZE])#临时卷积核
s = [] # 把(x,y)周围像素(也就是对应卷积区域)的像素放入列表s
for X in range(SIZE):
for Y in range(SIZE):
# Mask[X,Y] = temp[x+X][y+Y]
s.append(temp[x + X][y + Y])
s.sort()
return int(s[SIZE * SIZE // 2 + 1]) # 返回中位数
"""*********************************************** START ***********************************************************"""
# img = Image.open("E:\\a2.jpg").convert('L')#读取图片并转为灰度图
# img = Image.open(path).convert('L')#读取图片并转为灰度图
img = cv2.imread(path, 0)
img2 = cv2.imread(path, 0)
h, w = img.shape # 行 列
# 生成一个带有padding的临时模板图像
TempArry = np.zeros([h + padding * 2, w + padding * 2]) # 生成一个和图像对应的加入padding的空白模板
TempArry[padding:h + padding, padding:w + padding] = img # 把图像嵌入空白模板的padding内
# 循环对像素进行逐像素中值滤波
for x in range(0, h):
for y in range(0, w):
img[x, y] = Middle(TempArry, x, y, SIZE) # 卷积后的中位数赋值给原灰度图像素
cv2.imshow("Middle", img)
cv2.imshow("SRC", img2)
cv2.waitKey()
左图为带有噪声的输入原图,右图为经过中值滤波后的图
4.Canny边缘检测
import numpy as np
import cv2
"""
# ******高斯平滑******
sigma1 = sigma2 = 1
sum = 0
gaussian = np.zeros([5, 5])
for i in range(5):
for j in range(5):
gaussian[i, j] = math.exp(-1 / 2 * (np.square(i - 2) / np.square(sigma1) # 生成二维高斯分布矩阵
+ (np.square(j - 2) / np.square(sigma2)))) / (2 * math.pi * sigma1 * sigma2)
sum = sum + gaussian[i, j]
gaussian = gaussian / sum
plt.show()
"""
# 转化为灰度值图像
"""****************************** 一 读取三通道图 ****************************"""
img = cv2.imread(r'C:\Users\Administrator\Desktop\GUI2\fun\images\world.jpg', 1)
# img5 = cv2.imread('E:\\Lenna1.jpg',0)
"""***************************** 二 通道图转换成灰度图 ***************************"""
def rgb2gray(rgb):
return np.dot(rgb[..., :3], [0.114, 0.587, 0.299])
gray = rgb2gray(img) # 灰度图
W, H = gray.shape
"""***************************** 三 生成梯度图 ***************************"""
new_gray = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯模糊
# 求梯度幅值
W1, H1 = new_gray.shape
dx = np.zeros([W1 - 1, H1 - 1])
dy = np.zeros([W1 - 1, H1 - 1])
d = np.zeros([W1 - 1, H1 - 1]) # 图像幅度值
# dx1 = np.zeros([W1 - 1, H1 - 1])
# dy1 = np.zeros([W1 - 1, H1 - 1])
# d1 = np.zeros([W1 - 1, H1 - 1])#图像幅度值
for i in range(W1 - 1):
for j in range(H1 - 1):
dy[i, j] = new_gray[i + 1, j] - new_gray[i, j]
dx[i, j] = new_gray[i, j + 1] - new_gray[i, j]
d[i, j] = np.sqrt(np.square(dx[i, j]) + np.square(dy[i, j])) # 图像梯度幅值作为图像强度值
# dy1[i, j] = new_gray2[i + 1, j] - new_gray2[i, j]
# dx1[i, j] = new_gray2[i, j + 1] - new_gray2[i, j]
# d1[i, j] = np.sqrt(np.square(dx1[i, j]) + np.square(dy1[i, j])) # 图像梯度幅值作为图像强度值
"""***************************** 四 非极大值抑制 ***************************"""
W2, H2 = d.shape
NMS = np.copy(d)
NMS[0, :] = NMS[W2 - 1, :] = NMS[:, 0] = NMS[:, H2 - 1] = 0
for i in range(1, W2 - 1):
for j in range(1, H2 - 1):
if d[i, j] == 0:
NMS[i, j] = 0
else:
gradX = dx[i, j]
gradY = dy[i, j]
gradTemp = d[i, j]
# 如果Y方向幅度值较大
if np.abs(gradY) > np.abs(gradX):
weight = np.abs(gradX) / np.abs(gradY)
grad2 = d[i - 1, j]
grad4 = d[i + 1, j]
# 如果x,y方向梯度符号相同
if gradX * gradY > 0:
grad1 = d[i - 1, j - 1]
grad3 = d[i + 1, j + 1]
# 如果x,y方向梯度符号相反
else:
grad1 = d[i - 1, j + 1]
grad3 = d[i + 1, j - 1]
gradTemp1 = weight * grad1 + (1 - weight) * grad2
gradTemp2 = (1 - weight) * grad3 + weight * grad4
# 如果X方向幅度值较大
else:
weight = np.abs(gradY) / np.abs(gradX)
grad2 = d[i, j - 1]
grad4 = d[i, j + 1]
# 如果x,y方向梯度符号相同
if gradX * gradY > 0:
grad1 = d[i + 1, j - 1]
grad3 = d[i - 1, j + 1]
# 如果x,y方向梯度符号相反
else:
grad1 = d[i - 1, j - 1]
grad3 = d[i + 1, j + 1]
gradTemp1 = (1 - weight) * grad1 + weight * grad2
gradTemp2 = weight * grad3 + (1 - weight) * grad4
if gradTemp >= gradTemp1 and gradTemp >= gradTemp2:
NMS[i, j] = gradTemp
else:
NMS[i, j] = 0
"""***************************** 五 双阈值算法检测 ***************************"""
W3, H3 = NMS.shape
DT = np.zeros([W3, H3])
# 定义高低阈值
TL = 0.1 * np.max(NMS)
TH = 0.15 * np.max(NMS)
"""
当 “实际梯度 < 低阈值” 该点被录取为“非边缘点(背景点)
当 “实际梯度 > 高阈值” 该点被录取为“边缘点”
当低阈值< 实际梯度低< 高阈值 ” ,需要判别
如果周围八邻阈的点有大于高阈值的,凡梯度大者被录取为边缘点。
"""
for i in range(1, W3 - 1):
for j in range(1, H3 - 1):
if (NMS[i, j] < TL): # 小于低阈值背景点
DT[i, j] = 0
elif (NMS[i, j] > TH): # 大于高阈值边缘点
DT[i, j] = 255
elif (np.any((NMS[i - 1, j - 1:j + 2] > TH)).any() or NMS[i, j - 1] > TH or NMS[
i, j + 1] > TH # 低阈值 < x < 高阈值 比较八邻域
or np.any((NMS[i + 1, j - 1:j + 2] > TH))):
DT[i, j] = 255
cv2.imshow("gray", DT)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite("E:\\car_canny.jpg", DT)
上图为输入原图,下图为经过Canny边缘检测后的图
5.直方图规定化
import numpy as np
import cv2
import bisect
# 计算累计概率分布Pr
def get_Sk(Hist):
sum_Hist = sum(Hist)
Pr = Hist / sum_Hist
# 计算累计概率Sk
Sk = []
temp = 0
for n in Pr:
sk = temp + n
Sk.append(sk)
temp = sk
Sk = np.asarray(Sk)
return Sk
# 映射表 sk1(原图概率密度) sk2(目标图概率密度)
def get_lut(sk1, sk2):
index = np.zeros(256, dtype='uint8')
a = 0
for i in np.nditer(sk1):
subscript = bisect.bisect_right(sk2, i)
if subscript >= 256: # 防止二分查找出现256下标越界
subscript = 255
if abs(sk2[subscript] - i) < abs(sk2[subscript - 1] - i):
index[a] = subscript
else:
index[a] = subscript - 1
a = a + 1
return index
"""************************************* START ******************************************"""
Src = cv2.imread(r'C:\Users\Administrator\Desktop\GUI2\fun\images\MV.jpg', 0) # 输入图
Dist = cv2.imread(r'C:\Users\Administrator\Desktop\GUI2\fun\images\car.jpg', 0) # 目标图
# 1.获取原图和目标图直方图信息
SrcHist = cv2.calcHist([Src], [0], None, [256], [0, 255])
DistHist = cv2.calcHist([Dist], [0], None, [256], [0, 255])
# 2.计算原图直方图和目标图各灰度级累计概率密度
pr = get_Sk(SrcHist)
pz = get_Sk(DistHist)
# 3.生成 原图直方图累计概率密度 和目标直方图累计概率密度单映射表
SML = get_lut(pr, pz)
# 4.遍历图像各个灰度值,完成原图直方图到目标直方图类型的映射
h, w = Src.shape # 列 行
ImgOutput = Src.copy()
for i in range(0, h):
for j in range(0, w):
ImgOutput[i][j] = SML[ImgOutput[i][j]]
cv2.imshow("Src", Src)
cv2.imshow("Aim", Dist)
cv2.imshow("After", ImgOutput) # 规定花后的图
cv2.waitKey()
上图为原图
中图为规定目标图
下图为原图经过规定化后的图
6.PCA
import numpy as np
import cv2
path = r"C:\Users\Administrator\Desktop\GUI2\fun\images\MV.jpg"
# 读入 灰度图
img: np.ndarray = cv2.imread(path)
img = img[:, :, 0]
cv2.imshow("before", img)
# 预处理
h, w = img.shape
b = np.mean(img, axis=0)
STD = np.std(img, axis=0)
img = img - b
img = img / STD
img_T = img.transpose()
Mat = img_T.dot(img) # 得到 X * X_T
# 求 X * X_T 的前k大特征向量
eigvals, vecs = np.linalg.eig(Mat) # 注意求出的eigvals是特征值,vecs是标准化后的特征向量
indexs = np.argsort(eigvals)
indexs = indexs[::-1]
print(eigvals.shape)
# 编码矩阵 D 是前k大特征向量组成的矩阵(正交矩阵)——topk_evecs
k = 60 #取前60大特征值
topk_evecs: np.ndarray = vecs[:, indexs[0:k]]
print(topk_evecs.shape)
# X * D = 维度压缩后的图片信息——encode (信息由 512 x 512 压缩为 512 x 64)
encode = img.dot(topk_evecs)
# 译码矩阵即 D_T
img_new = ((encode.dot(topk_evecs.transpose()) * STD) + b).astype(np.uint8) # 译码得到的新图片
# print(img_new)
img_new[img_new < 0] = 0
img_new[img_new > 255] = 255
cv2.imshow("after", img_new)
cv2.waitKey(0)
左图为原输入图,右图为经过PCA后又还原回来的图(还原回来后图像信息有损失)
Mean_Shift(图像选小一些,参数R给大一些,运行就很慢这玩意一般都是调库,一般都是用C++实现的罕有Python实现)
import numpy as np
import cv2 as cv
# 图像预处理(放到数组中像素个数行5列的数组中)
def mean_shift(img):
rows, cols, channel = img.shape
rgb_array = np.zeros((rows * cols, 5))# (rows*cols)行 5列(RGBxy) 每一行保存一个像素点的信息
dstimg = np.zeros((rows, cols, channel))#原图同大小同深度的空白图像
k = 0
#遍历图像把每个像素点三通道RGB和坐标xy放入数组
for i in range(0, rows):
for j in range(0, cols):
rgb_array[k][0], rgb_array[k][1], rgb_array[k][2], rgb_array[k][3], rgb_array[k][4] = img[i][j][0], img[i][j][1], img[i][j][2], i, j
k += 1
# 聚类的圆形半径与收敛条件
r = 60# r是每次圆形聚类的半径
convergence = 50#每次圆形聚类收敛的条件 (中心点代替此次聚类半径中所有点)
temp_point = []#临时存放中心点r半径内所有像素点信息
#初始化标签flag,用来控制是否初始化中心点坐标
flag = False
while rgb_array.shape[0] != 0:
#在rows*cols个像素点中随机找出一个作为初始化中心点
if flag == False:
index_row = np.random.randint(0, rgb_array.shape[0]) # 任意寻找一个点作为开始的点
mean_r = rgb_array[index_row][0]
mean_g = rgb_array[index_row][1]
mean_b = rgb_array[index_row][2]
mean_x = rgb_array[index_row][3]
mean_y = rgb_array[index_row][4]
#对每个像素点进行遍历,找出在空间r内的点,
#并将像素值记录在temp_point中,下标信息记录在index中
index = []
for i in range(0, rgb_array.shape[0]):#按行遍历
#(该点到中心点的距离)(像素点的五个信息全部参与运算这样会更精确一些)
L = ((rgb_array[i][0] - mean_r) ** 2 + (rgb_array[i][1] - mean_g) ** 2 +(rgb_array[i][2] - mean_b) ** 2 + (rgb_array[i][3] - mean_x) ** 2 + (rgb_array[i][4] - mean_y) ** 2) ** 0.5
if L <= r:#该像素点在球半径r内,距离<所定半径
temp_point.append(rgb_array[i])#把符合条件的像素点信息与坐标储存起来
index.append(i)
#判断半径r内是否有像素点
if len(temp_point) > 0:
element0, element1, element2,element3, element4 = 0, 0, 0, 0, 0
#求偏移距离
#步骤:step1:将所有在半径内的像素点求和
# step2:对求和向量均值化,得到需要移动到终点
# step3:对均值化的求和向量减去中心点向量的模得到偏移距离(判断接下来是否进行漂移)
# step1 向量求和(半径r范围内所有像素点点的五行一列的点向量求和)
for i in range(0, len(temp_point)):
element0 += temp_point[i][0]
element1 += temp_point[i][1]
element2 += temp_point[i][2]
element3 += temp_point[i][3]
element4 += temp_point[i][4]
# step2 求和向量均值化,得到需要移动到终点坐标
element0 = element0 / len( temp_point)
element1 = element1 / len(temp_point)
element2 = element2 / len(temp_point)
element3 = element3 / len(temp_point)
element4 = element4 / len(temp_point)
# step2 终点坐标减去中心点向量取距离得偏移距离
new_L = ((element0 - mean_r) ** 2 + (element1 - mean_g) ** 2 + (element2 - mean_b) ** 2 + (element3 - mean_x) ** 2 + (element4 - mean_y) ** 2) ** 0.5
#偏移距离超参
#小于convergence,停止漂移,并将原图中所有半径<r像素点用中心点去替代他们,并赋值给空白图像dstimg
#大于convergence,继续漂移中心点继续更新
# step3 偏移距离小于等于convergence停止漂移
if new_L <= convergence:
for i in range(0, len(index)):
#返回原图片对应的行列,在新图的相同位置使用终点位置值代替
row = int((temp_point[i])[3])
col = int((temp_point[i])[4])
dstimg[row][col][0] = element0
dstimg[row][col][1] = element1
dstimg[row][col][2] = element2
rgb_array = np.delete(rgb_array, index, 0)#按行删除(index储存的就是要删除的每一行)
flag = False
#中心点更新
else:
mean_r = element0
mean_g = element1
mean_b = element2
mean_x = element3
mean_y = element4
flag = True
#清空上一轮r半径的漂移,准备下一轮
temp_point = []
#将dstimg像素值大小规定在 0-255范围内
dstimg = np.array(dstimg, np.uint8)
return dstimg
path7 = r'C:\Users\Administrator\Desktop\GUI2\fun\images\home.jpg'
src = cv.imread(path7)
#src2 = dist = cv.bilateralFilter(src, 0, 40, 15)
img = mean_shift(src)
#img2 = mean_shift(src2)
cv.imshow("SRC",src)
cv.imshow("Out",img)
#cv.imshow("Out2",img2)
cv.waitKey()
左图为输入图像,右图为输入图聚类后的图像
2.可视化界面
这里我就用阿里云盘了:某度网盘不开户员,下载龟爬速度
函数可视化界面下载链接:GUI3
https://www.aliyundrive.com/s/8fyiWHb4Sny
这里是引用
运行下图中的那个红框的py文件就能出现下面这个界面。以上函数用到的测试图像在下面包里的images文件夹下。
这里我还给这个可视化界面打个包