├── preview-mac.png ├── preview-windows.png ├── README.md ├── propertyWindow.py ├── propertyWindow.ui ├── mainWindow.ui ├── mainWindow.py └── main.py /preview-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-zxx/ImageProcessor/HEAD/preview-mac.png -------------------------------------------------------------------------------- /preview-windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-zxx/ImageProcessor/HEAD/preview-windows.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageProcessor 2 | 基于PyQt+OpenCV的图像处理软件 3 | ### 效果预览 4 | ![](preview-windows.png) 5 | ![](preview-mac.png) 6 | ### 环境搭建 7 | Python3.8 + PyQt5 + opencv-python + matplotlib 8 | #### 安装Python 9 | Linux:
10 | ``` 11 | apt install python3.8 12 | ``` 13 | 或者 14 | ``` 15 | yum install python3.8 16 | ``` 17 | Windows:直接拿exe安装,记得加系统环境path 18 | #### 安装依赖库 19 | ``` 20 | pip install PyQt5 21 | pip install opencv-python 22 | pip install matplotlib 23 | ``` 24 | -------------------------------------------------------------------------------- /propertyWindow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'propertyWindow.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.4 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(300, 200) 18 | Form.setMinimumSize(QtCore.QSize(300, 200)) 19 | Form.setMaximumSize(QtCore.QSize(300, 200)) 20 | Form.setWindowTitle("") 21 | self.gridLayout = QtWidgets.QGridLayout(Form) 22 | self.gridLayout.setObjectName("gridLayout") 23 | self.submitButton = QtWidgets.QPushButton(Form) 24 | self.submitButton.setObjectName("submitButton") 25 | self.gridLayout.addWidget(self.submitButton, 1, 1, 1, 1) 26 | self.horizontalLayout = QtWidgets.QHBoxLayout() 27 | self.horizontalLayout.setObjectName("horizontalLayout") 28 | self.propertyLabel = QtWidgets.QLabel(Form) 29 | self.propertyLabel.setObjectName("propertyLabel") 30 | self.horizontalLayout.addWidget(self.propertyLabel) 31 | self.spinBox = QtWidgets.QSpinBox(Form) 32 | self.spinBox.setMinimum(-100) 33 | self.spinBox.setMaximum(100) 34 | self.spinBox.setSingleStep(1) 35 | self.spinBox.setProperty("value", 0) 36 | self.spinBox.setObjectName("spinBox") 37 | self.horizontalLayout.addWidget(self.spinBox) 38 | self.slider = QtWidgets.QSlider(Form) 39 | self.slider.setEnabled(True) 40 | font = QtGui.QFont() 41 | font.setKerning(True) 42 | self.slider.setFont(font) 43 | self.slider.setCursor(QtGui.QCursor(QtCore.Qt.SizeHorCursor)) 44 | self.slider.setAcceptDrops(False) 45 | self.slider.setMinimum(-100) 46 | self.slider.setMaximum(100) 47 | self.slider.setSingleStep(1) 48 | self.slider.setPageStep(1) 49 | self.slider.setTracking(True) 50 | self.slider.setOrientation(QtCore.Qt.Horizontal) 51 | self.slider.setInvertedAppearance(False) 52 | self.slider.setInvertedControls(False) 53 | self.slider.setObjectName("slider") 54 | self.horizontalLayout.addWidget(self.slider) 55 | self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 3) 56 | 57 | self.retranslateUi(Form) 58 | QtCore.QMetaObject.connectSlotsByName(Form) 59 | 60 | def retranslateUi(self, Form): 61 | _translate = QtCore.QCoreApplication.translate 62 | self.submitButton.setText(_translate("Form", "确定")) 63 | self.propertyLabel.setText(_translate("Form", "亮度")) 64 | -------------------------------------------------------------------------------- /propertyWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 300 10 | 200 11 | 12 | 13 | 14 | 15 | 300 16 | 200 17 | 18 | 19 | 20 | 21 | 300 22 | 200 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 | -100 49 | 50 | 51 | 100 52 | 53 | 54 | 1 55 | 56 | 57 | 0 58 | 59 | 60 | 61 | 62 | 63 | 64 | true 65 | 66 | 67 | 68 | true 69 | 70 | 71 | 72 | SizeHorCursor 73 | 74 | 75 | false 76 | 77 | 78 | -100 79 | 80 | 81 | 100 82 | 83 | 84 | 1 85 | 86 | 87 | 1 88 | 89 | 90 | true 91 | 92 | 93 | Qt::Horizontal 94 | 95 | 96 | false 97 | 98 | 99 | false 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /mainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 896 10 | 577 11 | 12 | 13 | 14 | 图像处理软件 15 | 16 | 17 | 18 | 19 | 20 | 21 | Qt::ScrollBarAlwaysOn 22 | 23 | 24 | Qt::ScrollBarAlwaysOn 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 0 33 | 0 34 | 35 | 36 | 37 | Qt::ScrollBarAlwaysOn 38 | 39 | 40 | Qt::ScrollBarAlwaysOn 41 | 42 | 43 | 44 | 45 | 46 | 47 | 原图预览 48 | 49 | 50 | Qt::AlignCenter 51 | 52 | 53 | 54 | 55 | 56 | 57 | 处理后的图片预览 58 | 59 | 60 | Qt::AlignCenter 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 0 70 | 0 71 | 896 72 | 24 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 | gg 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 | gg 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 | gg 297 | 298 | 299 | 300 | 301 | gg 302 | 303 | 304 | 305 | 306 | 均值滤波器 307 | 308 | 309 | 310 | 311 | 中值滤波器 312 | 313 | 314 | 315 | 316 | Sobel算子 317 | 318 | 319 | 320 | 321 | Prewitt算子 322 | 323 | 324 | 325 | 326 | 拉普拉斯算子 327 | 328 | 329 | 330 | 331 | 加均匀噪声 332 | 333 | 334 | 335 | 336 | 加脉冲噪声 337 | 338 | 339 | 340 | 341 | 342 | 343 | -------------------------------------------------------------------------------- /mainWindow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'mainWindow.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.9 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_MainWindow(object): 15 | def setupUi(self, MainWindow): 16 | MainWindow.setObjectName("MainWindow") 17 | MainWindow.resize(896, 577) 18 | self.centralwidget = QtWidgets.QWidget(MainWindow) 19 | self.centralwidget.setObjectName("centralwidget") 20 | self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) 21 | self.gridLayout.setObjectName("gridLayout") 22 | self.outImageView = QtWidgets.QGraphicsView(self.centralwidget) 23 | self.outImageView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) 24 | self.outImageView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) 25 | self.outImageView.setObjectName("outImageView") 26 | self.gridLayout.addWidget(self.outImageView, 1, 1, 1, 1) 27 | self.srcImageView = QtWidgets.QGraphicsView(self.centralwidget) 28 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 29 | sizePolicy.setHorizontalStretch(0) 30 | sizePolicy.setVerticalStretch(0) 31 | sizePolicy.setHeightForWidth(self.srcImageView.sizePolicy().hasHeightForWidth()) 32 | self.srcImageView.setSizePolicy(sizePolicy) 33 | self.srcImageView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) 34 | self.srcImageView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) 35 | self.srcImageView.setObjectName("srcImageView") 36 | self.gridLayout.addWidget(self.srcImageView, 1, 0, 1, 1) 37 | self.srcImageLabel = QtWidgets.QLabel(self.centralwidget) 38 | self.srcImageLabel.setAlignment(QtCore.Qt.AlignCenter) 39 | self.srcImageLabel.setObjectName("srcImageLabel") 40 | self.gridLayout.addWidget(self.srcImageLabel, 0, 0, 1, 1) 41 | self.outImageLabel = QtWidgets.QLabel(self.centralwidget) 42 | self.outImageLabel.setAlignment(QtCore.Qt.AlignCenter) 43 | self.outImageLabel.setObjectName("outImageLabel") 44 | self.gridLayout.addWidget(self.outImageLabel, 0, 1, 1, 1) 45 | MainWindow.setCentralWidget(self.centralwidget) 46 | self.menubar = QtWidgets.QMenuBar(MainWindow) 47 | self.menubar.setGeometry(QtCore.QRect(0, 0, 896, 24)) 48 | self.menubar.setObjectName("menubar") 49 | self.fileMenu = QtWidgets.QMenu(self.menubar) 50 | self.fileMenu.setObjectName("fileMenu") 51 | self.resetImageMenu = QtWidgets.QMenu(self.menubar) 52 | self.resetImageMenu.setObjectName("resetImageMenu") 53 | self.aboutMenu = QtWidgets.QMenu(self.menubar) 54 | self.aboutMenu.setObjectName("aboutMenu") 55 | self.grayMappingMenu = QtWidgets.QMenu(self.menubar) 56 | self.grayMappingMenu.setObjectName("grayMappingMenu") 57 | self.operateImageMenu = QtWidgets.QMenu(self.menubar) 58 | self.operateImageMenu.setObjectName("operateImageMenu") 59 | self.histogramMenu = QtWidgets.QMenu(self.menubar) 60 | self.histogramMenu.setObjectName("histogramMenu") 61 | self.noiseMenu = QtWidgets.QMenu(self.menubar) 62 | self.noiseMenu.setObjectName("noiseMenu") 63 | self.filterMenu = QtWidgets.QMenu(self.menubar) 64 | self.filterMenu.setObjectName("filterMenu") 65 | self.smoothMenu = QtWidgets.QMenu(self.filterMenu) 66 | self.smoothMenu.setObjectName("smoothMenu") 67 | self.sharpMenu = QtWidgets.QMenu(self.filterMenu) 68 | self.sharpMenu.setObjectName("sharpMenu") 69 | MainWindow.setMenuBar(self.menubar) 70 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 71 | self.statusbar.setObjectName("statusbar") 72 | MainWindow.setStatusBar(self.statusbar) 73 | self.openFileAction = QtWidgets.QAction(MainWindow) 74 | self.openFileAction.setObjectName("openFileAction") 75 | self.saveFileAction = QtWidgets.QAction(MainWindow) 76 | self.saveFileAction.setObjectName("saveFileAction") 77 | self.saveFileAsAction = QtWidgets.QAction(MainWindow) 78 | self.saveFileAsAction.setObjectName("saveFileAsAction") 79 | self.exitAppAction = QtWidgets.QAction(MainWindow) 80 | self.exitAppAction.setObjectName("exitAppAction") 81 | self.resetImageAction = QtWidgets.QAction(MainWindow) 82 | self.resetImageAction.setObjectName("resetImageAction") 83 | self.aboutAction = QtWidgets.QAction(MainWindow) 84 | self.aboutAction.setObjectName("aboutAction") 85 | self.actiongg_2 = QtWidgets.QAction(MainWindow) 86 | self.actiongg_2.setObjectName("actiongg_2") 87 | self.grayAction = QtWidgets.QAction(MainWindow) 88 | self.grayAction.setObjectName("grayAction") 89 | self.binaryAction = QtWidgets.QAction(MainWindow) 90 | self.binaryAction.setObjectName("binaryAction") 91 | self.reverseAction = QtWidgets.QAction(MainWindow) 92 | self.reverseAction.setObjectName("reverseAction") 93 | self.imageAddAction = QtWidgets.QAction(MainWindow) 94 | self.imageAddAction.setObjectName("imageAddAction") 95 | self.imageSubtractAction = QtWidgets.QAction(MainWindow) 96 | self.imageSubtractAction.setObjectName("imageSubtractAction") 97 | self.imageMultiplyAction = QtWidgets.QAction(MainWindow) 98 | self.imageMultiplyAction.setObjectName("imageMultiplyAction") 99 | self.histogramAction = QtWidgets.QAction(MainWindow) 100 | self.histogramAction.setObjectName("histogramAction") 101 | self.histogramEqAction = QtWidgets.QAction(MainWindow) 102 | self.histogramEqAction.setObjectName("histogramEqAction") 103 | self.lightAction = QtWidgets.QAction(MainWindow) 104 | self.lightAction.setObjectName("lightAction") 105 | self.contrastAction = QtWidgets.QAction(MainWindow) 106 | self.contrastAction.setObjectName("contrastAction") 107 | self.sharpAction = QtWidgets.QAction(MainWindow) 108 | self.sharpAction.setObjectName("sharpAction") 109 | self.zoomAction = QtWidgets.QAction(MainWindow) 110 | self.zoomAction.setObjectName("zoomAction") 111 | self.rotateAction = QtWidgets.QAction(MainWindow) 112 | self.rotateAction.setObjectName("rotateAction") 113 | self.actiongg = QtWidgets.QAction(MainWindow) 114 | self.actiongg.setObjectName("actiongg") 115 | self.saturationAction = QtWidgets.QAction(MainWindow) 116 | self.saturationAction.setObjectName("saturationAction") 117 | self.hueAction = QtWidgets.QAction(MainWindow) 118 | self.hueAction.setObjectName("hueAction") 119 | self.reColorAction = QtWidgets.QAction(MainWindow) 120 | self.reColorAction.setObjectName("reColorAction") 121 | self.addGaussianNoiseAction = QtWidgets.QAction(MainWindow) 122 | self.addGaussianNoiseAction.setObjectName("addGaussianNoiseAction") 123 | self.actiongg_3 = QtWidgets.QAction(MainWindow) 124 | self.actiongg_3.setObjectName("actiongg_3") 125 | self.actiongg_4 = QtWidgets.QAction(MainWindow) 126 | self.actiongg_4.setObjectName("actiongg_4") 127 | self.meanValueAction = QtWidgets.QAction(MainWindow) 128 | self.meanValueAction.setObjectName("meanValueAction") 129 | self.medianValueAction = QtWidgets.QAction(MainWindow) 130 | self.medianValueAction.setObjectName("medianValueAction") 131 | self.sobelAction = QtWidgets.QAction(MainWindow) 132 | self.sobelAction.setObjectName("sobelAction") 133 | self.prewittAction = QtWidgets.QAction(MainWindow) 134 | self.prewittAction.setObjectName("prewittAction") 135 | self.laplacianAction = QtWidgets.QAction(MainWindow) 136 | self.laplacianAction.setObjectName("laplacianAction") 137 | self.addUiformNoiseAction = QtWidgets.QAction(MainWindow) 138 | self.addUiformNoiseAction.setObjectName("addUiformNoiseAction") 139 | self.addImpulseNoiseAction = QtWidgets.QAction(MainWindow) 140 | self.addImpulseNoiseAction.setObjectName("addImpulseNoiseAction") 141 | self.fileMenu.addAction(self.openFileAction) 142 | self.fileMenu.addAction(self.saveFileAction) 143 | self.fileMenu.addAction(self.saveFileAsAction) 144 | self.fileMenu.addSeparator() 145 | self.fileMenu.addAction(self.exitAppAction) 146 | self.resetImageMenu.addAction(self.resetImageAction) 147 | self.aboutMenu.addAction(self.aboutAction) 148 | self.grayMappingMenu.addAction(self.grayAction) 149 | self.grayMappingMenu.addAction(self.binaryAction) 150 | self.grayMappingMenu.addAction(self.reverseAction) 151 | self.grayMappingMenu.addSeparator() 152 | self.grayMappingMenu.addAction(self.lightAction) 153 | self.grayMappingMenu.addAction(self.contrastAction) 154 | self.grayMappingMenu.addAction(self.sharpAction) 155 | self.grayMappingMenu.addAction(self.saturationAction) 156 | self.grayMappingMenu.addAction(self.hueAction) 157 | self.operateImageMenu.addAction(self.imageAddAction) 158 | self.operateImageMenu.addAction(self.imageSubtractAction) 159 | self.operateImageMenu.addAction(self.imageMultiplyAction) 160 | self.operateImageMenu.addSeparator() 161 | self.operateImageMenu.addAction(self.zoomAction) 162 | self.operateImageMenu.addAction(self.rotateAction) 163 | self.histogramMenu.addAction(self.histogramAction) 164 | self.histogramMenu.addAction(self.histogramEqAction) 165 | self.noiseMenu.addAction(self.addGaussianNoiseAction) 166 | self.noiseMenu.addAction(self.addUiformNoiseAction) 167 | self.noiseMenu.addAction(self.addImpulseNoiseAction) 168 | self.smoothMenu.addAction(self.meanValueAction) 169 | self.smoothMenu.addAction(self.medianValueAction) 170 | self.sharpMenu.addAction(self.sobelAction) 171 | self.sharpMenu.addAction(self.prewittAction) 172 | self.sharpMenu.addAction(self.laplacianAction) 173 | self.filterMenu.addAction(self.smoothMenu.menuAction()) 174 | self.filterMenu.addAction(self.sharpMenu.menuAction()) 175 | self.menubar.addAction(self.fileMenu.menuAction()) 176 | self.menubar.addAction(self.resetImageMenu.menuAction()) 177 | self.menubar.addAction(self.grayMappingMenu.menuAction()) 178 | self.menubar.addAction(self.operateImageMenu.menuAction()) 179 | self.menubar.addAction(self.histogramMenu.menuAction()) 180 | self.menubar.addAction(self.noiseMenu.menuAction()) 181 | self.menubar.addAction(self.filterMenu.menuAction()) 182 | self.menubar.addAction(self.aboutMenu.menuAction()) 183 | 184 | self.retranslateUi(MainWindow) 185 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 186 | 187 | def retranslateUi(self, MainWindow): 188 | _translate = QtCore.QCoreApplication.translate 189 | MainWindow.setWindowTitle(_translate("MainWindow", "图像处理软件")) 190 | self.srcImageLabel.setText(_translate("MainWindow", "原图预览")) 191 | self.outImageLabel.setText(_translate("MainWindow", "处理后的图片预览")) 192 | self.fileMenu.setTitle(_translate("MainWindow", "文件")) 193 | self.resetImageMenu.setTitle(_translate("MainWindow", "重置图片")) 194 | self.aboutMenu.setTitle(_translate("MainWindow", "关于")) 195 | self.grayMappingMenu.setTitle(_translate("MainWindow", "直接灰度映射")) 196 | self.operateImageMenu.setTitle(_translate("MainWindow", "图像运算")) 197 | self.histogramMenu.setTitle(_translate("MainWindow", "直方图均衡")) 198 | self.noiseMenu.setTitle(_translate("MainWindow", "噪声")) 199 | self.filterMenu.setTitle(_translate("MainWindow", "空域滤波")) 200 | self.smoothMenu.setTitle(_translate("MainWindow", "平滑滤波器")) 201 | self.sharpMenu.setTitle(_translate("MainWindow", "锐化滤波器")) 202 | self.openFileAction.setText(_translate("MainWindow", "打开")) 203 | self.saveFileAction.setText(_translate("MainWindow", "保存")) 204 | self.saveFileAsAction.setText(_translate("MainWindow", "另存为")) 205 | self.exitAppAction.setText(_translate("MainWindow", "退出")) 206 | self.resetImageAction.setText(_translate("MainWindow", "恢复到原始图片")) 207 | self.aboutAction.setText(_translate("MainWindow", "关于作者")) 208 | self.actiongg_2.setText(_translate("MainWindow", "gg")) 209 | self.grayAction.setText(_translate("MainWindow", "灰度化")) 210 | self.binaryAction.setText(_translate("MainWindow", "二值化")) 211 | self.reverseAction.setText(_translate("MainWindow", "颜色反转")) 212 | self.imageAddAction.setText(_translate("MainWindow", "加")) 213 | self.imageSubtractAction.setText(_translate("MainWindow", "减")) 214 | self.imageMultiplyAction.setText(_translate("MainWindow", "乘")) 215 | self.histogramAction.setText(_translate("MainWindow", "归一化直方图")) 216 | self.histogramEqAction.setText(_translate("MainWindow", "直方图均衡化")) 217 | self.lightAction.setText(_translate("MainWindow", "亮度")) 218 | self.contrastAction.setText(_translate("MainWindow", "对比度")) 219 | self.sharpAction.setText(_translate("MainWindow", "锐度")) 220 | self.zoomAction.setText(_translate("MainWindow", "缩放")) 221 | self.rotateAction.setText(_translate("MainWindow", "旋转")) 222 | self.actiongg.setText(_translate("MainWindow", "gg")) 223 | self.saturationAction.setText(_translate("MainWindow", "饱和度")) 224 | self.hueAction.setText(_translate("MainWindow", "色调")) 225 | self.reColorAction.setText(_translate("MainWindow", "重新着色")) 226 | self.addGaussianNoiseAction.setText(_translate("MainWindow", "加高斯噪声")) 227 | self.actiongg_3.setText(_translate("MainWindow", "gg")) 228 | self.actiongg_4.setText(_translate("MainWindow", "gg")) 229 | self.meanValueAction.setText(_translate("MainWindow", "均值滤波器")) 230 | self.medianValueAction.setText(_translate("MainWindow", "中值滤波器")) 231 | self.sobelAction.setText(_translate("MainWindow", "Sobel算子")) 232 | self.prewittAction.setText(_translate("MainWindow", "Prewitt算子")) 233 | self.laplacianAction.setText(_translate("MainWindow", "拉普拉斯算子")) 234 | self.addUiformNoiseAction.setText(_translate("MainWindow", "加均匀噪声")) 235 | self.addImpulseNoiseAction.setText(_translate("MainWindow", "加脉冲噪声")) 236 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import sys 4 | 5 | import cv2 6 | import numpy 7 | import numpy as np 8 | from PyQt5 import QtGui, QtCore 9 | from PyQt5.QtGui import QImage, QPixmap 10 | from PyQt5.QtWidgets import QMainWindow, QWidget, QApplication, QGraphicsScene, QFileDialog, QMessageBox 11 | from matplotlib import pyplot as plt 12 | 13 | from mainWindow import Ui_MainWindow 14 | from propertyWindow import Ui_Form 15 | 16 | 17 | # 预处理窗口类 18 | # 就是弹出来的调整各种属性值的小窗口 19 | class PropertyWindow(QWidget, Ui_Form): 20 | # 信号只能在Object的子类中创建,并且只能在创建类的时候的时候添加,而不能等类已经定义完再作为动态属性添加进去。 21 | # 自定义的信号在__init__()函数之前定义。 22 | # 自定义一个信号signal,有一个object类型的参数 23 | # **** 鬼知道咋回事,想用str类型但会出bug,只好用object了 **** 24 | signal = QtCore.pyqtSignal(object) 25 | 26 | # 类初始化 27 | def __init__(self): 28 | # 调用父类的初始化 29 | super(PropertyWindow, self).__init__() 30 | # 窗口界面初始化 31 | self.setupUi(self) 32 | 33 | # 绑定窗口组件响应事件的处理函数(将窗口中的组件被用户触发的点击、值变化等事件绑定到处理函数) 34 | # 数值框的值改变 35 | self.spinBox.valueChanged.connect(self.__spinBoxChange) 36 | # 滑动条的值改变 37 | self.slider.valueChanged.connect(self.__sliderChange) 38 | # 点击确认按钮 39 | self.submitButton.clicked.connect(self.__valueConfirm) 40 | 41 | # 数值框值改变的处理函数 42 | def __spinBoxChange(self): 43 | # 获取数值框的当前值 44 | value = self.spinBox.value() 45 | # 与滑动条进行数值同步 46 | self.slider.setValue(value) 47 | # 发送信号到主窗口,参数是当前数值(主窗口有自定义的接收并处理该信号的函数) 48 | self.signal.emit(value) 49 | 50 | # 滑动条值改变的处理函数 51 | def __sliderChange(self): 52 | # 获取滑动条的当前值 53 | value = self.slider.value() 54 | # 与数值框进行数值同步 55 | # 注意:该操作也会触发数值框的数值改变,即会触发调用__spinBoxChange(),所以不要需要在此处重复发信号到主窗口 56 | self.spinBox.setValue(value) 57 | 58 | # 确认按钮按下的处理函数 59 | def __valueConfirm(self): 60 | # 发送确认修改信号 61 | self.signal.emit('ok') 62 | # 关闭窗口 63 | self.close() 64 | 65 | # 重写窗口关闭处理 66 | def closeEvent(self, a0: QtGui.QCloseEvent) -> None: 67 | # 发送取消修改信号,关闭窗口时触发 68 | self.signal.emit('close') 69 | 70 | 71 | # 主窗口类 72 | class MainWindow(QMainWindow, Ui_MainWindow): 73 | # 类初始化 74 | def __init__(self): 75 | # 调用父类的初始化 76 | super(MainWindow, self).__init__() 77 | # 窗口界面初始化 78 | self.setupUi(self) 79 | 80 | # 图像属性调整的子窗口,初始化默认空(亮度、对比度、锐度、饱和度、色调、旋转、缩放调节窗口) 81 | self.__propertyWindow = None 82 | 83 | # 当前打开的图片文件名,初始化默认空 84 | self.__fileName = None 85 | 86 | # 保存图片原始数据,初始化默认空 87 | self.__srcImageRGB = None 88 | # 保存图片最终处理结果的数据,初始化默认空 89 | self.__outImageRGB = None 90 | # 保存图片暂时修改的数据,初始化默认空 91 | # (在修改图像属性未点击确认时,需要暂存修改数据,如果确认后将临时数据同步为最终结果数据,如果未确认将复原数据) 92 | self.__tempImageRGB = None 93 | 94 | # 绑定窗口事件的响应函数 95 | # 文件菜单 96 | # 打开文件 97 | self.openFileAction.triggered.connect(self.__openFileAndShowImage) 98 | # 保存文件 99 | self.saveFileAction.triggered.connect(self.saveFile) 100 | # 另存为文件 101 | self.saveFileAsAction.triggered.connect(self.saveFileAs) 102 | # 退出程序 103 | self.exitAppAction.triggered.connect(self.close) 104 | 105 | # 重置图像菜单 106 | # 重置图像 107 | self.resetImageAction.triggered.connect(self.__resetImage) 108 | 109 | # 直接灰度映射菜单 110 | # 灰度化 111 | self.grayAction.triggered.connect(self.__toGrayImage) 112 | # 二值化 113 | self.binaryAction.triggered.connect(self.__toBinaryImage) 114 | # 颜色反转 115 | self.reverseAction.triggered.connect(self.__reverseImage) 116 | # 亮度调整 117 | self.lightAction.triggered.connect(self.__openLightWindow) 118 | # 对比度调整 119 | self.contrastAction.triggered.connect(self.__openContrastWindow) 120 | # 锐度调整 121 | self.sharpAction.triggered.connect(self.__openSharpWindow) 122 | # 饱和度调整 123 | self.saturationAction.triggered.connect(self.__openSaturationWindow) 124 | # 色度调整 125 | self.hueAction.triggered.connect(self.__openHueWindow) 126 | 127 | # 图像运算菜单 128 | # 加 129 | self.imageAddAction.triggered.connect(self.__addImage) 130 | # 减 131 | self.imageSubtractAction.triggered.connect(self.__subtractImage) 132 | # 乘 133 | self.imageMultiplyAction.triggered.connect(self.__multiplyImage) 134 | # 缩放 135 | self.zoomAction.triggered.connect(self.__openZoomWindow) 136 | # 旋转 137 | self.rotateAction.triggered.connect(self.__openRotateWindow) 138 | 139 | # 直方图均衡菜单 140 | # 归一化直方图 141 | self.histogramAction.triggered.connect(self.__histogram) 142 | # 直方图均衡化 143 | self.histogramEqAction.triggered.connect(self.__histogramEqualization) 144 | 145 | # 噪声菜单 146 | # 加高斯噪声 147 | self.addGaussianNoiseAction.triggered.connect(self.__addGasussNoise) 148 | # 加均匀噪声 149 | self.addUiformNoiseAction.triggered.connect(self.__addUniformNoise) 150 | # 加脉冲(椒盐)噪声 151 | self.addImpulseNoiseAction.triggered.connect(self.__addImpulseNoise) 152 | 153 | # 空域滤波菜单 154 | # 均值滤波 155 | self.meanValueAction.triggered.connect(self.__meanValueFilter) 156 | # 中值滤波 157 | self.medianValueAction.triggered.connect(self.__medianValueFilter) 158 | # Sobel算子锐化 159 | self.sobelAction.triggered.connect(self.__sobel) 160 | # Prewitt算子锐化 161 | self.prewittAction.triggered.connect(self.__prewitt) 162 | # 拉普拉斯算子锐化 163 | self.laplacianAction.triggered.connect(self.__laplacian) 164 | 165 | # 关于菜单 166 | # 关于作者 167 | self.aboutAction.triggered.connect(self.__aboutAuthor) 168 | 169 | # -----------------------------------文件----------------------------------- 170 | # 打开文件并在主窗口中显示打开的图像 171 | def __openFileAndShowImage(self): 172 | # 打开文件选择窗口 173 | __fileName, _ = QFileDialog.getOpenFileName(self, '选择图片', '.', 'Image Files(*.png *.jpeg *.jpg *.bmp)') 174 | # 文件存在 175 | if __fileName and os.path.exists(__fileName): 176 | # 设置打开的文件名属性 177 | self.__fileName = __fileName 178 | # 转换颜色空间,cv2默认打开BGR空间,Qt界面显示需要RGB空间,所以就统一到RGB吧 179 | # __bgrImg = cv2.imread(self.__fileName) 180 | # cv2 读取不了有中文名的图像文件 ! 181 | # 所以用numpy读取数据,再用cv2.imdecode解码数据来解决。 182 | __bgrImg = cv2.imdecode(np.fromfile(self.__fileName, dtype=np.uint8), -1) 183 | # 设置初始化数据 184 | self.__srcImageRGB = cv2.cvtColor(__bgrImg, cv2.COLOR_BGR2RGB) 185 | self.__outImageRGB = self.__srcImageRGB.copy() 186 | self.__tempImageRGB = self.__srcImageRGB.copy() 187 | # 在窗口中左侧QGraphicsView区域显示图片 188 | self.__drawImage(self.srcImageView, self.__srcImageRGB) 189 | # 在窗口中右侧QGraphicsView区域显示图片 190 | self.__drawImage(self.outImageView, self.__srcImageRGB) 191 | 192 | # 在窗口中指定的QGraphicsView区域(左或右)显示指定类型(rgb、灰度、二值)的图像 193 | def __drawImage(self, location, img): 194 | # RBG图 195 | if len(img.shape) > 2: 196 | # 获取行(高度)、列(宽度)、通道数 197 | __height, __width, __channel = img.shape 198 | # 转换为QImage对象,注意第四、五个参数 199 | __qImg = QImage(img, __width, __height, __width * __channel, QImage.Format_RGB888) 200 | # 灰度图、二值图 201 | else: 202 | # 获取行(高度)、列(宽度)、通道数 203 | __height, __width = img.shape 204 | # 转换为QImage对象,注意第四、五个参数 205 | __qImg = QImage(img, __width, __height, __width, QImage.Format_Indexed8) 206 | 207 | # 创建QPixmap对象 208 | __qPixmap = QPixmap.fromImage(__qImg) 209 | # 创建显示容器QGraphicsScene对象 210 | __scene = QGraphicsScene() 211 | # 填充QGraphicsScene对象 212 | __scene.addPixmap(__qPixmap) 213 | # 将QGraphicsScene对象设置到QGraphicsView区域实现图片显示 214 | location.setScene(__scene) 215 | 216 | # 执行保存图片文件的操作 217 | def __saveImg(self, fileName): 218 | # 已经打开了文件才能保存 219 | if fileName: 220 | # RGB转BRG空间后才能通过opencv正确保存 221 | __bgrImg = cv2.cvtColor(self.__outImageRGB, cv2.COLOR_RGB2BGR) 222 | # 保存 223 | cv2.imwrite(fileName, __bgrImg) 224 | # 消息提示窗口 225 | QMessageBox.information(self, '提示', '文件保存成功!') 226 | else: 227 | # 消息提示窗口 228 | QMessageBox.information(self, '提示', '文件保存失败!') 229 | 230 | # 保存文件,覆盖原始文件 231 | def saveFile(self): 232 | self.__saveImg(self.__fileName) 233 | 234 | # 文件另存 235 | def saveFileAs(self): 236 | # 已经打开了文件才能保存 237 | if self.__fileName: 238 | # 打开文件保存的选择窗口 239 | __fileName, _ = QFileDialog.getSaveFileName(self, '保存图片', 'Image', 240 | 'Image Files(*.png *.jpeg *.jpg *.bmp)') 241 | self.__saveImg(__fileName) 242 | else: 243 | # 消息提示窗口 244 | QMessageBox.information(self, '提示', '文件保存失败!') 245 | 246 | # 重写窗口关闭事件函数,来关闭所有窗口。因为默认关闭主窗口子窗口依然存在。 247 | def closeEvent(self, a0: QtGui.QCloseEvent) -> None: 248 | sys.exit(0) 249 | 250 | # -----------------------------------重置图片----------------------------------- 251 | # 重置图片到初始状态 252 | def __resetImage(self): 253 | if self.__fileName: 254 | # 还原文件打开时的初始化图片数据 255 | self.__outImageRGB = self.__srcImageRGB.copy() 256 | # 窗口显示图片 257 | self.__drawImage(self.outImageView, self.__outImageRGB) 258 | 259 | # -----------------------------------图像预处理----------------------------------- 260 | # 灰度化 261 | def __toGrayImage(self): 262 | # 只有RGB图才能灰度化 263 | if self.__fileName and len(self.__outImageRGB.shape) > 2: 264 | # 灰度化使得三通道RGB图变成单通道灰度图 265 | self.__outImageRGB = cv2.cvtColor(self.__outImageRGB, cv2.COLOR_RGB2GRAY) 266 | self.__drawImage(self.outImageView, self.__outImageRGB) 267 | 268 | # 二值化 269 | def __toBinaryImage(self): 270 | # 先灰度化 271 | self.__toGrayImage() 272 | if self.__fileName: 273 | # 后阈值化为二值图 274 | _, self.__outImageRGB = cv2.threshold(self.__outImageRGB, 127, 255, cv2.THRESH_BINARY) 275 | self.__drawImage(self.outImageView, self.__outImageRGB) 276 | 277 | # 反转图片颜色 278 | def __reverseImage(self): 279 | if self.__fileName: 280 | self.__outImageRGB = cv2.bitwise_not(self.__outImageRGB) 281 | self.__drawImage(self.outImageView, self.__outImageRGB) 282 | 283 | # 执行打开属性调节子窗口(亮度、对比度、锐度、饱和度、色调、缩放、旋转) 284 | def __openPropertyWindow(self, propertyName, func): 285 | if self.__fileName: 286 | if self.__propertyWindow: 287 | self.__propertyWindow.close() 288 | self.__propertyWindow = PropertyWindow() 289 | # 设置窗口内容 290 | self.__propertyWindow.setWindowTitle(propertyName) 291 | self.__propertyWindow.propertyLabel.setText(propertyName) 292 | # 接收信号 293 | # 设置主窗口接收子窗口发送的信号的处理函数 294 | self.__propertyWindow.signal.connect(func) 295 | # 禁用主窗口菜单栏,子窗口置顶,且无法切换到主窗口 296 | self.__propertyWindow.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) 297 | self.__propertyWindow.setWindowModality(QtCore.Qt.ApplicationModal) 298 | # 显示子窗口 299 | self.__propertyWindow.show() 300 | 301 | # 亮度调节子窗口 302 | def __openLightWindow(self): 303 | self.__openPropertyWindow('亮度', self.__changeLight) 304 | 305 | # 对比度调节子窗口 306 | def __openContrastWindow(self): 307 | self.__openPropertyWindow('对比度', self.__changeContrast) 308 | 309 | # 锐度调节子窗口 310 | def __openSharpWindow(self): 311 | self.__openPropertyWindow('锐度', self.__changeSharp) 312 | 313 | # 饱和度调节子窗口 314 | def __openSaturationWindow(self): 315 | self.__openPropertyWindow('饱和度', self.__changeSaturation) 316 | 317 | # 色调调节子窗口 318 | def __openHueWindow(self): 319 | self.__openPropertyWindow('色调', self.__changeHue) 320 | 321 | # 预处理信号 322 | def __dealSignal(self, val): 323 | # 拷贝后修改副本 324 | __img = self.__outImageRGB.copy() 325 | # 如果是灰度图要转为RGB图 326 | if len(__img.shape) < 3: 327 | __img = cv2.cvtColor(__img, cv2.COLOR_GRAY2RGB) 328 | 329 | value = str(val) 330 | # 确认修改 331 | if value == 'ok': 332 | # 将暂存的修改保存为结果 333 | self.__outImageRGB = self.__tempImageRGB.copy() 334 | return None 335 | # 修改完成(确认已经做的修改或取消了修改) 336 | elif value == 'close': 337 | # 重绘修改预览 338 | self.__drawImage(self.outImageView, self.__outImageRGB) 339 | return None 340 | # 暂时修改 341 | else: 342 | return __img 343 | 344 | # 执行改变亮度或对比度 345 | # g(i,j)=αf(i,j)+(1-α)black+β,α用来调节对比度, β用来调节亮度 346 | def __lightAndContrast(self, img, alpha, beta): 347 | if len(img.shape) < 3: 348 | img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) 349 | rows, cols, channels = img.shape 350 | # 新建全零(黑色)图片数组 351 | blank = numpy.zeros([rows, cols, channels], img.dtype) 352 | # 计算两个图像阵列的加权和 353 | img = cv2.addWeighted(img, alpha, blank, 1 - alpha, beta) 354 | # 显示修改数据 355 | self.__drawImage(self.outImageView, img) 356 | return img 357 | 358 | # 修改亮度 359 | def __changeLight(self, val): 360 | # 预处理接收到的信号 361 | __img = self.__dealSignal(val) 362 | # 如果修改了属性值 363 | # None的size是1 !!! 364 | if numpy.size(__img) > 1: 365 | beta = int(val) * (255 / 100) 366 | # 暂存本次修改 367 | self.__tempImageRGB = self.__lightAndContrast(__img, 1, beta) 368 | 369 | # 修改对比度 370 | def __changeContrast(self, val): 371 | # 预处理接收到的信号 372 | __img = self.__dealSignal(val) 373 | # 如果修改了属性值 374 | if numpy.size(__img) > 1: 375 | k = int(val) 376 | if k != -100: 377 | alpha = (k + 100) / 100 378 | else: 379 | alpha = 0.01 380 | # 暂存本次修改 381 | self.__tempImageRGB = self.__lightAndContrast(__img, alpha, 0) 382 | 383 | # 修改锐度 384 | def __changeSharp(self, val): 385 | # 预处理接收到的信号 386 | __img = self.__dealSignal(val) 387 | # 如果修改了属性值 388 | if numpy.size(__img) > 1: 389 | # 比例 390 | k = int(val) * 0.01 391 | if k != 0: 392 | # 卷积核(拉普拉斯算子) 393 | kernel = numpy.array([[-1, -1, -1], [-1, 9 + k, -1], [-1, -1, -1]]) 394 | # 通过卷积实现锐化,暂存修改数据 395 | self.__tempImageRGB = cv2.filter2D(__img, -1, kernel) 396 | else: 397 | self.__tempImageRGB = self.__outImageRGB.copy() 398 | # 显示修改数据 399 | self.__drawImage(self.outImageView, self.__tempImageRGB) 400 | 401 | # 修改饱和度 402 | def __changeSaturation(self, val): 403 | # 预处理接收到的信号 404 | __img = self.__dealSignal(val) 405 | # 如果修改了属性值 406 | if numpy.size(__img) > 1: 407 | # 转换颜色空间到HLS 408 | __img = cv2.cvtColor(__img, cv2.COLOR_RGB2HLS) 409 | # 比例 410 | k = int(val) * (255 / 100) 411 | # 切片修改S分量,并限制色彩数值在0-255之间 412 | __img[:, :, 2] = numpy.clip(__img[:, :, 2] + k, 0, 255) 413 | # 暂存修改数据 414 | self.__tempImageRGB = cv2.cvtColor(__img, cv2.COLOR_HLS2RGB) 415 | # 显示修改数据 416 | self.__drawImage(self.outImageView, self.__tempImageRGB) 417 | 418 | # 修改色调 419 | # OpenCV中hue通道的取值范围是0 - 180 420 | def __changeHue(self, val): 421 | # 预处理接收到的信号 422 | __img = self.__dealSignal(val) 423 | # 如果修改了属性值 424 | if numpy.size(__img) > 1: 425 | # 转换颜色空间到HLS 426 | __img = cv2.cvtColor(__img, cv2.COLOR_RGB2HLS) 427 | # 比例 428 | k = int(val) * (90 / 100) 429 | # 切片修改H分量,并限制色彩数值在0-180之间 430 | __img[:, :, 0] = (__img[:, :, 0] + k) % 180 431 | # 暂存修改数据 432 | self.__tempImageRGB = cv2.cvtColor(__img, cv2.COLOR_HLS2RGB) 433 | # 显示修改数据 434 | self.__drawImage(self.outImageView, self.__tempImageRGB) 435 | 436 | # -----------------------------------图像运算----------------------------------- 437 | # 加、减、乘操作 438 | def __operation(self, func): 439 | if self.__fileName: 440 | __fileName, _ = QFileDialog.getOpenFileName(self, '选择图片', '.', 'Image Files(*.png *.jpeg *.jpg *.bmp)') 441 | if __fileName and os.path.exists(__fileName): 442 | # __bgrImg = cv2.imread(__fileName) 443 | __bgrImg = cv2.imdecode(np.fromfile(__fileName, dtype=np.uint8), -1) 444 | # 图片尺寸相同才能进行运算 445 | if self.__outImageRGB.shape == __bgrImg.shape: 446 | __rgbImg = cv2.cvtColor(__bgrImg, cv2.COLOR_BGR2RGB) 447 | self.__outImageRGB = func(self.__outImageRGB, __rgbImg) 448 | self.__drawImage(self.outImageView, self.__outImageRGB) 449 | else: 450 | QMessageBox.information(None, '提示', '图像尺寸不一致,无法进行操作!') 451 | 452 | # 加 453 | def __addImage(self): 454 | self.__operation(cv2.add) 455 | 456 | # 减 457 | def __subtractImage(self): 458 | self.__operation(cv2.subtract) 459 | 460 | # 乘 461 | def __multiplyImage(self): 462 | self.__operation(cv2.multiply) 463 | 464 | # 缩放调节子窗口 465 | def __openZoomWindow(self): 466 | self.__openPropertyWindow('缩放', self.__changeZoom) 467 | 468 | # 缩放 469 | def __changeZoom(self, val): 470 | # 预处理接收到的信号 471 | __img = self.__dealSignal(val) 472 | # 如果修改了属性值 473 | # None的size是1 !!! why??? 474 | if numpy.size(__img) > 1: 475 | # 计算比例 476 | i = int(val) 477 | if i == -100: 478 | k = 0.01 479 | elif i >= 0: 480 | k = (i + 10) / 10 481 | else: 482 | k = (i + 100) / 100 483 | # 直接cv2.resize()缩放 484 | self.__tempImageRGB = cv2.resize(__img, None, fx=k, fy=k, interpolation=cv2.INTER_LINEAR) 485 | # 显示修改数据 486 | self.__drawImage(self.outImageView, self.__tempImageRGB) 487 | 488 | # 旋转调节子窗口 489 | def __openRotateWindow(self): 490 | self.__openPropertyWindow('旋转', self.__changeRotate) 491 | if self.__fileName: 492 | # 重设属性值取值范围 493 | self.__propertyWindow.slider.setMaximum(360) 494 | self.__propertyWindow.slider.setMinimum(-360) 495 | self.__propertyWindow.spinBox.setMaximum(360) 496 | self.__propertyWindow.spinBox.setMinimum(-360) 497 | 498 | # 旋转 499 | def __changeRotate(self, val): 500 | # 预处理接收到的信号 501 | __img = self.__dealSignal(val) 502 | # 如果修改了属性值 503 | # None的size是1 !!! why??? 504 | if numpy.size(__img) > 1: 505 | # 比例 506 | k = int(val) 507 | (h, w) = __img.shape[:2] 508 | (cX, cY) = (w // 2, h // 2) 509 | # 绕图片中心旋转 510 | m = cv2.getRotationMatrix2D((cX, cY), k, 1.0) 511 | # 计算调整后的图片显示大小,使得图片不会被切掉边缘 512 | cos = numpy.abs(m[0, 0]) 513 | sin = numpy.abs(m[0, 1]) 514 | nW = int((h * sin) + (w * cos)) 515 | nH = int((h * cos) + (w * sin)) 516 | m[0, 2] += (nW / 2) - cX 517 | m[1, 2] += (nH / 2) - cY 518 | # 变换,并设置旋转调整后产生的无效区域为白色 519 | self.__tempImageRGB = __img = cv2.warpAffine(__img, m, (nW, nH), borderValue=(255, 255, 255)) 520 | # 显示修改数据 521 | self.__drawImage(self.outImageView, self.__tempImageRGB) 522 | 523 | # -----------------------------------直方图均衡----------------------------------- 524 | # 归一化直方图 525 | def __histogram(self): 526 | if self.__fileName: 527 | # 如果是灰度图 528 | if len(self.__outImageRGB.shape) < 3: 529 | # __hist = cv2.calcHist([self.__outImageRGB], [0], None, [256], [0, 256]) 530 | # __hist /= self.__outImageRGB.shape[0] * self.__outImageRGB.shape[1] 531 | # plt.plot(__hist) 532 | # 使用 matplotlib 的绘图功能同时绘制单通道的直方图 533 | # density的类型是 bool型,指定为True,则为频率直方图,反之为频数直方图 534 | plt.hist(self.__outImageRGB.ravel(), bins=255, rwidth=0.8, range=(0, 256), density=True) 535 | # 如果是RGB图 536 | else: 537 | color = {'r', 'g', 'b'} 538 | # 使用 matplotlib 的绘图功能同时绘制多通道 RGB 的直方图 539 | for i, col in enumerate(color): 540 | __hist = cv2.calcHist([self.__outImageRGB], [i], None, [256], [0, 256]) 541 | __hist /= self.__outImageRGB.shape[0] * self.__outImageRGB.shape[1] 542 | plt.plot(__hist, color=col) 543 | # x轴长度区间 544 | plt.xlim([0, 256]) 545 | # 显示直方图 546 | plt.show() 547 | 548 | # 直方图均衡化 549 | def __histogramEqualization(self): 550 | if self.__fileName: 551 | # 如果是灰度图 552 | if len(self.__outImageRGB.shape) < 3: 553 | self.__outImageRGB = cv2.equalizeHist(self.__outImageRGB) 554 | # 如果是RGB图 555 | else: 556 | # 分解通道,各自均衡化,再合并通道 557 | (r, g, b) = cv2.split(self.__outImageRGB) 558 | rh = cv2.equalizeHist(r) 559 | gh = cv2.equalizeHist(g) 560 | bh = cv2.equalizeHist(b) 561 | self.__outImageRGB = cv2.merge((rh, gh, bh)) 562 | self.__drawImage(self.outImageView, self.__outImageRGB) 563 | 564 | # -----------------------------------噪声----------------------------------- 565 | # 加高斯噪声 566 | def __addGasussNoise(self): 567 | if self.__fileName: 568 | # 图片灰度标准化 569 | self.__outImageRGB = numpy.array(self.__outImageRGB / 255, dtype=float) 570 | # 产生高斯噪声 571 | noise = numpy.random.normal(0, 0.001 ** 0.5, self.__outImageRGB.shape) 572 | # 叠加图片和噪声 573 | out = cv2.add(self.__outImageRGB, noise) 574 | # 还原灰度并截取灰度区间 575 | self.__outImageRGB = numpy.clip(numpy.uint8(out * 255), 0, 255) 576 | self.__drawImage(self.outImageView, self.__outImageRGB) 577 | 578 | # 加均匀噪声 579 | def __addUniformNoise(self): 580 | if self.__fileName: 581 | # 起始范围 582 | low = 100 583 | # 终止范围 584 | height = 150 585 | # 搞一个与图片同规模数组 586 | out = numpy.zeros(self.__outImageRGB.shape, numpy.uint8) 587 | # 噪声生成比率 588 | ratio = 0.05 589 | # 遍历图片 590 | for i in range(self.__outImageRGB.shape[0]): 591 | for j in range(self.__outImageRGB.shape[1]): 592 | # 随机数[0.0,1.0) 593 | r = random.random() 594 | # 填充黑点 595 | if r < ratio: 596 | # 生成[low,height]的随机值 597 | out[i][j] = random.randint(low, height) 598 | # 填充白点 599 | elif r > 1 - ratio: 600 | out[i][j] = random.randint(low, height) 601 | # 填充原图 602 | else: 603 | out[i][j] = self.__outImageRGB[i][j] 604 | self.__outImageRGB = out.copy() 605 | self.__drawImage(self.outImageView, self.__outImageRGB) 606 | 607 | # 加脉冲噪声 608 | def __addImpulseNoise(self): 609 | if self.__fileName: 610 | # 搞一个与图片同规模数组 611 | out = numpy.zeros(self.__outImageRGB.shape, numpy.uint8) 612 | # 椒盐噪声生成比率 613 | ratio = 0.05 614 | # 遍历图片 615 | for i in range(self.__outImageRGB.shape[0]): 616 | for j in range(self.__outImageRGB.shape[1]): 617 | # 随机数[0.0,1.0) 618 | r = random.random() 619 | # 填充黑点 620 | if r < ratio: 621 | out[i][j] = 0 622 | # 填充白点 623 | elif r > 1 - ratio: 624 | out[i][j] = 255 625 | # 填充原图 626 | else: 627 | out[i][j] = self.__outImageRGB[i][j] 628 | self.__outImageRGB = out.copy() 629 | self.__drawImage(self.outImageView, self.__outImageRGB) 630 | 631 | # -----------------------------------空域滤波----------------------------------- 632 | # 均值滤波 633 | def __meanValueFilter(self): 634 | if self.__fileName: 635 | # 直接调库 636 | self.__outImageRGB = cv2.blur(self.__outImageRGB, (5, 5)) 637 | self.__drawImage(self.outImageView, self.__outImageRGB) 638 | 639 | # 中值滤波 640 | def __medianValueFilter(self): 641 | if self.__fileName: 642 | # 直接调库 643 | self.__outImageRGB = cv2.medianBlur(self.__outImageRGB, 5) 644 | self.__drawImage(self.outImageView, self.__outImageRGB) 645 | 646 | # Sobel算子锐化 647 | def __sobel(self): 648 | if self.__fileName: 649 | # 直接调库 650 | self.__outImageRGB = cv2.Sobel(self.__outImageRGB, -1, 1, 1, 3) 651 | self.__drawImage(self.outImageView, self.__outImageRGB) 652 | 653 | # Prewitt算子锐化 654 | def __prewitt(self): 655 | if self.__fileName: 656 | # Prewitt 算子 657 | kernelx = numpy.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]], dtype=int) 658 | kernely = numpy.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=int) 659 | # 通过自定义卷积核实现卷积 660 | imgx = cv2.filter2D(self.__outImageRGB, -1, kernelx) 661 | imgy = cv2.filter2D(self.__outImageRGB, -1, kernely) 662 | # 合并 663 | self.__outImageRGB = cv2.add(imgx, imgy) 664 | self.__drawImage(self.outImageView, self.__outImageRGB) 665 | 666 | # 拉普拉斯算子锐化 667 | def __laplacian(self): 668 | if self.__fileName: 669 | # 直接调库 670 | self.__outImageRGB = cv2.Laplacian(self.__outImageRGB, -1, ksize=3) 671 | self.__drawImage(self.outImageView, self.__outImageRGB) 672 | 673 | # -----------------------------------关于----------------------------------- 674 | # 关于作者 675 | def __aboutAuthor(self): 676 | QMessageBox.information(None, '关于作者', '图像处理软件2.1\n\nCopyright © 2021–2099 赵相欣\n\n保留一切权利') 677 | 678 | 679 | if __name__ == '__main__': 680 | app = QApplication(sys.argv) 681 | mainWindow = MainWindow() 682 | mainWindow.show() 683 | sys.exit(app.exec()) 684 | --------------------------------------------------------------------------------