forked from itisyang/ImageMiniLab
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathImageMiniLab.py
379 lines (317 loc) · 14.9 KB
/
ImageMiniLab.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
'''
file: ImageMiniLab.py
date: 2019/04/19 20:00
author: itisyang@gmail.com
brief: opencv-python 应用
note: PyQt5 + Qt Designer + Python 3.7
'''
import cv2 as cv
import numpy as np
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QMessageBox
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
from ImageMiniLabUI import *
# 图像信息
def get_image_info(image):
print("图像类型:",type(image))
print("图像长x宽x通道数:",image.shape)
print("图像长宽通道数相乘所得值:",image.size)
print("图像像素值类型:",image.dtype)
pixel_data = np.array(image) # 将图像转换成数组
print("像素大小:", pixel_data)
def clamp(pv):
if pv > 255:
return 255
elif pv < 0:
return 0
else:
return pv
class ImageMiniLab(QMainWindow, Ui_ImageMiniLabUI):
def __init__(self, parent=None):
super(ImageMiniLab, self).__init__(parent)
self.setupUi(self)
self.SrcImgShowLabel.clear()
self.DstImgShowLabel.clear()
# self.SrcImgShowLabel.setScaledContents(True)
# self.DstImgShowLabel.setScaledContents(True)
self.src_file = "" # 图像原始路径,cv处理用
self.src_pix = QPixmap() # 未处理像素,显示用
self.dst_pix = QPixmap() # 处理后像素,显示用
# 实验内容,接口定义,实验入口
self.exp_type = {"选择实验类型":self.no_exp_type,
"灰度化":self.to_gray,
"反转": self.bitwise_not,
"通道分离": self.channels_split,
"噪声、滤波": self.noise_and_blur,
"高斯双边滤波": self.bilateral_filter,
"均值偏移滤波": self.mean_shift_filter,
"图像二值化": self.threshold,
"Canny边缘检测": self.canny_edge,
"直线检测": self.hough_line,
"圆检测": self.hough_circles,
"轮廓发现": self.find_contours,
"人脸识别": self.face_recognize}
self.ExpTypeComboBox.addItems(self.exp_type)
# 载入图像(初次)
def load_exp_img(self, img_name):
self.ExpImgLineEdit.setText(img_name)
if self.src_pix.load(img_name) is False:
QMessageBox.warning(self, "打开图片失败", "请更换图片")
return
else:
self.src_file = img_name
self.dst_pix = self.src_pix.copy(self.src_pix.rect())
self.resizeEvent(None)
self.show_exp_pix()
# 显示图像
def show_exp_pix(self):
if self.src_pix.width() > 0:
w = self.SrcImgShowLabel.width()
h = self.SrcImgShowLabel.height()
self.SrcImgShowLabel.setPixmap(self.src_pix.scaled(w, h, Qt.KeepAspectRatio))
if self.dst_pix.width() > 0:
w = self.DstImgShowLabel.width()
h = self.DstImgShowLabel.height()
self.DstImgShowLabel.setPixmap(self.dst_pix.scaled(w, h, Qt.KeepAspectRatio))
# 窗口大小变化,使显示内容适应窗口大小
def resizeEvent(self, event):
w = self.ImgShowWidget.width()
h = self.ImgShowWidget.height()
self.SrcImgShowLabel.resize(w/2, h)
self.SrcImgShowLabel.move(0, 0)
self.DstImgShowLabel.resize(w/2, h)
self.DstImgShowLabel.move(w/2, 0)
self.show_exp_pix()
# 装饰器,必须注明,不然槽会调用两次
@QtCore.pyqtSlot()
def on_SelectImgPushButton_clicked(self):
# 设置文件扩展名过滤,注意用双分号间隔
img_name, file_type = QFileDialog.getOpenFileName(self, "选取实验图片", ".", "All Files (*);;Images (*.png *.jpg *.bmp)")
print(img_name)
if len(img_name) == 0:
return
self.load_exp_img(img_name)
@QtCore.pyqtSlot()
def on_LoadTestDataPushButton_clicked(self):
# 测试参数
self.ExpTypeComboBox.setCurrentIndex(1)
test_img = './lena.jpg'
self.ExpImgLineEdit.setText(test_img)
self.load_exp_img(test_img)
@QtCore.pyqtSlot()
def on_GoExpPushButton_clicked(self):
cur_exp_type = self.ExpTypeComboBox.currentText()
print("实验类型:", cur_exp_type)
if cur_exp_type not in self.exp_type:
QMessageBox.warning(self, "实验类型出错", "实验类型不存在,或无实验处理步骤,请联系技术支持。")
# print("类型", type(self.exp_type[cur_exp_type]))
self.exp_type[cur_exp_type]()
@QtCore.pyqtSlot()
def on_ExpTypeComboBox_currentTextChanged(self, text):
if text == "通道分离":
# 显示对应的处理参数界面
pass
# 未选择实验类型
def no_exp_type(self):
QMessageBox.warning(self, "未选择实验类型", "请先选择实验类型。")
# cv读取图片
def cv_read_img(self, img):
src = cv.imread(img)
if src is None:
QMessageBox.warning(self, "载入出错", "图片读取失败。\n(可能原因:无图片、无正确权限、不受支持或未知的格式)")
return None
return src
def decode_and_show_dst(self, dst):
ret, img_buf = cv.imencode(".jpg", dst)
# print(ret, img_buf)
if ret is True:
ret = self.dst_pix.loadFromData(img_buf)
if ret is True:
self.show_exp_pix()
# 灰度化
def to_gray(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# print("类型", type(gray))
# get_image_info(gray)
self.decode_and_show_dst(gray)
# 反转
def bitwise_not(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
dst = cv.bitwise_not(src) # 按位取反,白变黑,黑变白
self.decode_and_show_dst(dst)
# 通道分离
def channels_split(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
b, g, r = cv.split(src)
merge_image = cv.merge([b, g, r])
"""创建三维数组,0维为B,1维为G,2维为R"""
height, width, channels = src.shape
img = np.zeros([height*2, width*2, channels], np.uint8)
img[0:height, 0:width] = np.expand_dims(b, axis=2)
img[0:height, width:width*2] = np.expand_dims(g, axis=2)
img[height:height*2, 0:width] = np.expand_dims(r, axis=2)
img[height:height*2, width:width*2] = merge_image
self.decode_and_show_dst(img)
'''
一些图像知识:
1. 噪声:主要有三种:
椒盐噪声(Salt & Pepper):含有随机出现的黑白亮度值。
脉冲噪声:只含有随机的正脉冲和负脉冲噪声。
高斯噪声:含有亮度服从高斯或正态分布的噪声。高斯噪声是很多传感器噪声的模型,如摄像机的电子干扰噪声。
2. 滤波器:主要两类:线性和非线性
线性滤波器:使用连续窗函数内像素加权和来实现滤波,同一模式的权重因子可以作用在每一个窗口内,即线性滤波器是空间不变的。
如果图像的不同部分使用不同的滤波权重因子,线性滤波器是空间可变的。因此可以使用卷积模板来实现滤波。
线性滤波器对去除高斯噪声有很好的效果。常用的线性滤波器有均值滤波器和高斯平滑滤波器。
(1) 均值滤波器:最简单均值滤波器是局部均值运算,即每一个像素只用其局部邻域内所有值的平均值来置换.
(2) 高斯平滑滤波器是一类根据高斯函数的形状来选择权值的线性滤波器。 高斯平滑滤波器对去除服从正态分布的噪声是很有效的。
非线性滤波器:
(1) 中值滤波器:均值滤波和高斯滤波运算主要问题是有可能模糊图像中尖锐不连续的部分。
中值滤波器的基本思想使用像素点邻域灰度值的中值来代替该像素点的灰度值,它可以去除脉冲噪声、椒盐噪声同时保留图像边缘细节。
中值滤波不依赖于邻域内与典型值差别很大的值,处理过程不进行加权运算。
中值滤波在一定条件下可以克服线性滤波器所造成的图像细节模糊,而对滤除脉冲干扰很有效。
(2) 边缘保持滤波器:由于均值滤波:平滑图像外还可能导致图像边缘模糊和中值滤波:去除脉冲噪声的同时可能将图像中的线条细节滤除。
边缘保持滤波器是在综合考虑了均值滤波器和中值滤波器的优缺点后发展起来的,它的特点是:
滤波器在除噪声脉冲的同时,又不至于使图像边缘十分模糊。
过程:分别计算[i,j]的左上角子邻域、左下角子邻域、右上角子邻域、右下角子邻域的灰度分布均匀度V;
然后取最小均匀度对应区域的均值作为该像素点的新灰度值。分布越均匀,均匀度V值越小。v=<(f(x, y) - f_(x, y))^2
'''
def noise_and_blur(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
# 加高斯噪声
h, w, c = src.shape
for row in range(h):
for col in range(w):
s = np.random.normal(0, 20, 3) # normal(loc=0.0, scale=1.0, size=None),均值,标准差,大小
b = src[row, col, 0]
g = src[row, col, 1]
r = src[row, col, 2]
src[row, col, 0] = clamp(b + s[0])
src[row, col, 1] = clamp(g + s[1])
src[row, col, 2] = clamp(r + s[2])
img = np.zeros([h * 2, w * 2, c], np.uint8)
img[0:h, 0:w] = src
# GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
# ksize表示卷积核大小,sigmaX,Y表示x,y方向上的标准差,这两者只需一个即可,并且ksize为大于0的奇数
dst = cv.GaussianBlur(src, (5, 5), 0) # 高斯模糊,sigmaX与ksize一个为0
img[0:h, w:w*2] = dst
self.decode_and_show_dst(img)
"""
同时考虑空间与信息和灰度相似性,达到保边去噪的目的
双边滤波的核函数是空间域核与像素范围域核的综合结果:
在图像的平坦区域,像素值变化很小,对应的像素范围域权重接近于1,此时空间域权重起主要作用,相当于进行高斯模糊;
在图像的边缘区域,像素值变化很大,像素范围域权重变大,从而保持了边缘的信息。
"""
# 高斯双边滤波
def bilateral_filter(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
dst = cv.bilateralFilter(src, 0, 100, 15)
self.decode_and_show_dst(dst)
# 均值偏移滤波
def mean_shift_filter(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
dst = cv.pyrMeanShiftFiltering(src, 10, 50) # 均值偏移滤波
self.decode_and_show_dst(dst)
# 图像二值化
def threshold(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 这个函数的第一个参数就是原图像,原图像应该是灰度图。
# 第二个参数就是用来对像素值进行分类的阈值。
# 第三个参数就是当像素值高于(有时是小于)阈值时应该被赋予的新的像素值
# 第四个参数来决定阈值方法,见threshold_simple()
# ret, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
ret, dst = cv.threshold(gray, 127, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
self.decode_and_show_dst(dst)
# Canny边缘检测
def canny_edge(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
blurred = cv.GaussianBlur(src, (3, 3), 0)
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
grad_x = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
grad_y = cv.Sobel(gray, cv.CV_16SC1, 0, 1)
dst = cv.Canny(grad_x, grad_y, 30, 150)
# dst = cv.Canny(gray, 50, 150)
self.decode_and_show_dst(dst)
# 直线检测
def hough_line(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 50, 150, apertureSize=3)
lines = cv.HoughLines(edges, 1, np.pi/180, 200)
for line in lines:
rho, theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0+1000*(-b))
y1 = int(y0+1000*(a))
x2 = int(x0-1000*(-b))
y2 = int(y0-1000*(a))
cv.line(src, (x1, y1), (x2, y2), (0, 0, 255), 2)
self.decode_and_show_dst(src)
# 圆检测
def hough_circles(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
dst = cv.pyrMeanShiftFiltering(src, 10, 100)
cimage = cv.cvtColor(dst, cv.COLOR_BGR2GRAY)
circles = cv.HoughCircles(cimage, cv.HOUGH_GRADIENT, 1, 20, param1=50, param2=30, minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
cv.circle(src, (i[0], i[1]), i[2], (0, 0, 255), 2)
cv.circle(src, (i[0], i[1]), 2, (255, 0, 255), 2)
self.decode_and_show_dst(src)
# 轮廓发现
def find_contours(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
dst = cv.GaussianBlur(src, (3, 3), 0)
gray = cv.cvtColor(dst, cv.COLOR_BGR2GRAY)
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cloneImage, contous, heriachy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
for i,contou in enumerate(contous):
cv.drawContours(src, contous, i, (0, 0, 255), 1)
# 轮廓
self.decode_and_show_dst(src)
# 轮廓覆盖
for i,contou in enumerate(contous):
cv.drawContours(src, contous, i, (0, 0, 255), -1)
self.decode_and_show_dst(src)
#人脸识别 正脸 需要下载xml模型 haarcascade_frontalface_alt.xml
def face_recognize(self):
src = self.cv_read_img(self.src_file)
if src is None:
return
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
face_cascade = cv.CascadeClassifier('haarcascade_frontalface_alt2.xml')
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.15,
minNeighbors=3,
minSize=(5, 5)
)
for (x, y, w, h) in faces:
cv.rectangle(src, (x, y), (x + w, y + h), (0, 255, 0), 2)
self.decode_and_show_dst(src)