You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

613 lines
20 KiB

# This Python file uses the following encoding: utf-8
import functools
import os
import sys
from math import sqrt
from pathlib import Path
from typing import Callable
import cv2
import matplotlib.pyplot as plt
import numpy as np
from PySide6.QtCore import Slot, QUrl, QPoint, QFileInfo
from PySide6.QtGui import QImage, QPainter, QColor
from PySide6.QtQml import QQmlApplicationEngine, QmlElement
from PySide6.QtQuick import QQuickPaintedItem
from PySide6.QtWidgets import QApplication
from numba import jit
from lapstyle import LapStylePredictor
QML_IMPORT_NAME = "Util"
QML_IMPORT_MAJOR_VERSION = 1
QML_IMPORT_MINOR_VERSION = 0
@QmlElement
class ImageItem(QQuickPaintedItem):
matrices: list[np.ndarray] = []
matrix: np.ndarray = None
image: QImage = None
xpos = 0
ypos = 0
def __init__(self, parent=None):
super(ImageItem, self).__init__(parent)
def paint(self, painter: QPainter):
if self.image is None:
painter.fillRect(self.boundingRect(), QColor("transparent"))
else:
x = self.width() / 2 - self.image.width() / 2
if x < 0:
x = 0
y = self.height() / 2 - self.image.height() / 2
if y < 0:
y = 0
painter.drawImage(x, y, self.image, self.xpos, self.ypos)
def setImage(self, matrix: np.ndarray):
self.matrix = matrix
# 彩色图片
if len(matrix.shape) > 2:
height, width, channel = matrix.shape
self.image = QImage(matrix.data, width, height, width * channel, QImage.Format_RGB888)
# 灰度图片
else:
height, width = matrix.shape
self.image = QImage(matrix.data, width, height, width, QImage.Format_Grayscale8)
self.update()
@Slot(float, float, result="QPoint")
def getPoint(self, x, y):
ox = np.round(x + self.xpos)
oy = np.round(y + self.ypos)
return QPoint(ox, oy)
def appendMatrix(self):
if self.matrix is None:
return
if len(self.matrices) >= 1 and np.array_equal(self.matrices[-1], self.matrix):
return
self.matrices.append(self.matrix)
@Slot()
def withdraw(self):
if self.matrix is None:
return
if np.array_equal(self.matrix, self.matrices[-1]) and len(self.matrices) >= 2:
self.matrices.pop()
self.setImage(self.matrices[-1])
@staticmethod
def process(isGray=False):
def decorator(func: Callable):
@functools.wraps(func)
def wrapper(self, *args):
image = self.getMatrix(isGray)
if image is None:
return
out = func(self, *args, src=image)
if out is not None:
self.setImage(out)
self.appendMatrix()
return wrapper
return decorator
def getMatrix(self, isGray=False):
if len(self.matrices) < 1:
return None
matrix = self.matrices[-1]
if not isGray or len(matrix.shape) < 3:
return matrix.copy()
return cv2.cvtColor(matrix, cv2.COLOR_RGB2GRAY)
@Slot(QUrl)
def readFile(self, filepath: QUrl):
image = cv2.imdecode(np.fromfile(filepath.toLocalFile(), dtype=np.uint8), cv2.IMREAD_COLOR)
matrix = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
self.setImage(matrix)
self.matrices.clear()
self.appendMatrix()
@Slot(QUrl)
def saveFile(self, filepath: QUrl):
if self.matrix is None:
return
if len(self.matrix.shape) > 2:
img = cv2.cvtColor(self.matrix, cv2.COLOR_RGB2BGR)
else:
img = self.matrix
fileInfo = QFileInfo(filepath.toLocalFile())
cv2.imencode("." + fileInfo.suffix(), img)[1].tofile(filepath.toLocalFile())
@Slot(int, int)
def moveBy(self, x: int, y: int):
if self.image is None:
return
if x + self.xpos + self.width() <= self.image.width():
ox = x + self.xpos
else:
ox = self.image.width() - self.width()
if ox < 0:
ox = 0
if y + self.ypos + self.height() <= self.image.height():
oy = y + self.ypos
else:
oy = self.image.height() - self.height()
if oy < 0:
oy = 0
if ox is not self.xpos or oy is not self.ypos:
self.xpos = ox
self.ypos = oy
self.update()
# 图像变换
@Slot(int)
@process()
def removeChannel(self, channel, src):
img = src
img[..., channel] = 0
return img
@Slot(int, int, QColor)
@process()
def translation(self, dx, dy, border: QColor, src):
img = src # 读取彩色图像(BGR)
rows, cols = img.shape[:2]
mat = np.float32([[1, 0, dx], [0, 1, dy]]) # 构造平移变换矩阵
(r, g, b, a) = border.getRgb()
dst = cv2.warpAffine(img, mat, (cols, rows), borderValue=(r, g, b, a))
return dst
@Slot(float)
@process()
def centralRotation(self, theta, src):
# 1.36 图像旋转 (以任意点 (x0,y0) 为中心旋转)
img = src
height, width = img.shape[:2] # 图片的高度和宽度
x0, y0 = width // 2, height // 2 # 以图像中心作为旋转中心
mat = cv2.getRotationMatrix2D((x0, y0), theta, 1.0)
dst = cv2.warpAffine(img, mat, (width, height)) # 设置白色填充
return dst
@Slot(int)
@process()
def rightAngleRotation(self, angle, src):
img = src
img_r = None
if angle == 1:
img_r = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
elif angle == 2:
img_r = cv2.rotate(img, cv2.ROTATE_180)
if angle == 3:
img_r = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
return img_r
@Slot(int)
@process()
def flip(self, method, src):
img = src
img_flip = None
if method == 1:
img_flip = cv2.flip(img, 0) # 垂直翻转
if method == 2:
img_flip = cv2.flip(img, 1) # 水平翻转
if method == 3:
img_flip = cv2.flip(img, -1) # 水平和垂直翻转
return img_flip
@Slot(float, float)
@process()
def resize(self, width_size, height_size, src):
img = src
dst = cv2.resize(img, None, fx=width_size, fy=height_size, interpolation=cv2.INTER_AREA)
return dst
@Slot()
@process()
def affine(self, src):
image = src
rows, cols = image.shape[:2]
pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) # 初始位置
pts2 = np.float32([[50, 100], [200, 50], [100, 250]]) # 终止位置
mtr = cv2.getAffineTransform(pts1, pts2) # 计算 2x3 变换矩阵 MA
dst = cv2.warpAffine(image, mtr, (cols, rows)) # 实现仿射变换
return dst
@Slot()
@process()
def shear(self, src):
# 1.41 图像的错切
img = src
height, width = img.shape[:2] # 图片的高度和宽度
mat = np.float32([[1, 0.2, 0], [0, 1, 0]]) # 构造错切变换矩阵
img_shear = cv2.warpAffine(img, mat, (width, height))
return img_shear
@Slot()
@process()
def projective(self, src):
img = src
h, w = img.shape[:2] # 图片的高度和宽度
point_src = np.float32([[0, 0], [w, 0], [0, h], [w, h]]) # 原始图像中 4点坐标
point_dst = np.float32(
[[int(w / 3), int(h / 3)], [int(w * 2 / 3), int(h / 3)], [0, h], [w, h]]) # 变换图像中 4点坐标
mp = cv2.getPerspectiveTransform(point_src, point_dst) # 计算投影变换矩阵 M
img_p = cv2.warpPerspective(img, mp, (w, h), flags=cv2.INTER_AREA, borderMode=cv2.BORDER_WRAP)
return img_p
# 图形绘制
@Slot(QPoint, QPoint, QColor, int)
@process()
def line(self, start: QPoint, end: QPoint, color: QColor, width, src):
img = src
if start.isNull() or end.isNull():
return
(r, g, b, a) = color.getRgb()
cv2.line(img, start.toTuple(), end.toTuple(), (r, g, b, a), width)
return img
@Slot(QPoint, QPoint, QColor, int)
@process()
def rectangle(self, start: QPoint, end: QPoint, color: QColor, width, src):
img = src
if start.isNull() or end.isNull():
return
(r, g, b, a) = color.getRgb()
cv2.rectangle(img, start.toTuple(), end.toTuple(), (r, g, b, a), width)
return img
@Slot(QPoint, QPoint, QColor, int)
@process()
def circle(self, center: QPoint, side: QPoint, color: QColor, width, src):
img = src
if center.isNull() or side.isNull():
return
(center_x, center_y) = center.toTuple()
(side_x, side_y) = side.toTuple()
radius = np.round(sqrt(np.power(center_x - side_x, 2) + np.power(center_y - side_y, 2)))
(r, g, b, a) = color.getRgb()
cv2.circle(img, center.toTuple(), int(radius), (r, g, b, a), width)
return img
@Slot(QPoint, QColor, int, int, str)
@process()
def text(self, start: QPoint, color: QColor, fontsize, width, text, src):
img = src
if start.isNull():
return
(r, g, b, a) = color.getRgb()
cv2.putText(img, text, start.toTuple(), cv2.FONT_HERSHEY_SCRIPT_COMPLEX, fontsize, (r, g, b, a), width,
cv2.LINE_AA, 0)
return img
# 灰度运算
@Slot(int)
@process(isGray=True)
def binaryTransformation(self, thresh, src):
img_gray = src
if img_gray is None:
return
_, img = cv2.threshold(img_gray, thresh, 255, cv2.THRESH_BINARY) # 转换为二值图像, thresh=191
return img
@Slot()
@process(isGray=True)
def linearGrayscaleTransformation(self, src):
# 通过遍历对不同像素范围内进行分段线性变化,在这里分三段函数进行分段线性变化, 主要还是考察for循环的应用
# y=0.5*x(x<50)
# y=3.6*x-310(50<=x<150)
# y=0.238*x+194(x>=150)
img = src
@jit(nopython=True)
def transform(image):
out = np.zeros(image.shape, np.uint8)
for pos, pix in np.ndenumerate(image):
if pix < 50:
out[pos] = 0.5 * pix
elif pix < 150:
out[pos] = 3.6 * pix - 310
else:
out[pos] = 0.238 * pix + 194
return out
res = transform(img)
return res
@Slot(int, int)
@process(isGray=True)
def grayLayered(self, low_thresh, high_thresh, src):
# # 1.53 分段线性灰度变换 (灰度级分层) # Gray layered
img_gray = src
'''
另一种方法
img_layer1 = img_gray.copy()
img_layer1[(img_layer1[:, :] < low_thresh) | (img_layer1[:, :] > high_thresh)] = 0 # 其它区域:黑色
img_layer1[(img_layer1[:, :] >= low_thresh) & (img_layer1[:, :] <= high_thresh)] = 255 # 灰度级窗口:白色
'''
img_layer = img_gray.copy()
img_layer[(img_layer[:, :] >= low_thresh) & (img_layer[:, :] <= high_thresh)] = 255 # 灰度级窗口:白色,其它区域不变
return img_layer
@Slot()
@process(isGray=True)
def logTrans(self, src):
# 1.55 图像的非线性灰度变换:对数变换
img = src
img_log = np.log(img + 1.0)
cv2.normalize(img_log, img_log, 0, 255, cv2.NORM_MINMAX)
img_log_abs = cv2.convertScaleAbs(img_log)
return img_log_abs
@Slot(float)
@process(isGray=True)
def gammaTrans(self, gamma, src):
# 1.56 图像的非线性灰度变换: 幂律变换 (伽马变换)
img = src
img_gamma = np.power(img, gamma)
cv2.normalize(img_gamma, img_gamma, 0, 255, cv2.NORM_MINMAX)
img_gamma_abs = cv2.convertScaleAbs(img_gamma)
return img_gamma_abs
@Slot()
@process(isGray=True)
def grayHistogram(self, src):
# 1.57 图像的灰度直方图
img = src
hist_cv = cv2.calcHist([img], [0], None, [256], [0, 256]) # OpenCV 函数 cv2.calcHist
plt.bar(range(256), hist_cv[:, 0])
'''
用opencv和numpy两种方法得到直方图
hist_np, bins = np.histogram(img.flatten(), 256)
plt.subplot(132,xticks=[], yticks=[])
plt.axis([0,255,0,np.max(hist_cv)])
plt.bar(range(256), hist_cv[:,0])
plt.title("Gray Hist(cv2.calcHist)")
plt.subplot(133,xticks=[], yticks=[])
plt.axis([0,255,0,np.max(hist_cv)])
plt.bar(bins[:-1], hist_np)
plt.title("Gray Hist(np.histogram)")
'''
plt.show()
# 边缘检测
@Slot()
@process(isGray=True)
def roberts(self, src):
img = src
# Roberts 边缘算子
kernel_Roberts_x = np.array([[1, 0], [0, -1]])
kernel_Roberts_y = np.array([[0, -1], [1, 0]])
imgRoberts_x = cv2.filter2D(img, cv2.CV_16S, kernel_Roberts_x)
imgRoberts_y = cv2.filter2D(img, cv2.CV_16S, kernel_Roberts_y)
imgRoberts_x_abs = cv2.convertScaleAbs(imgRoberts_x)
imgRoberts_y_abs = cv2.convertScaleAbs(imgRoberts_y)
imgRoberts = cv2.addWeighted(imgRoberts_x_abs, 0.5, imgRoberts_y_abs, 0.5, 0)
return imgRoberts
@Slot()
@process(isGray=True)
def sobel(self, src):
img = src
# kernel_Sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
# kernel_Sobel_y = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
imgSobel_x = cv2.Sobel(img, cv2.CV_16S, 1, 0)
imgSobel_y = cv2.Sobel(img, cv2.CV_16S, 0, 1)
imgSobel_x_abs = cv2.convertScaleAbs(imgSobel_x)
imgSobel_y_abs = cv2.convertScaleAbs(imgSobel_y)
imgSobel = cv2.addWeighted(imgSobel_x_abs, 0.5, imgSobel_y_abs, 0.5, 0)
return imgSobel
@Slot()
@process(isGray=True)
def prewitt(self, src):
img = src
kernel_Prewitt_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]])
kernel_Prewitt_y = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]])
imgPrewitt_x = cv2.filter2D(img, cv2.CV_16S, kernel_Prewitt_x)
imgPrewitt_y = cv2.filter2D(img, cv2.CV_16S, kernel_Prewitt_y)
imgPrewitt_x_abs = cv2.convertScaleAbs(imgPrewitt_x)
imgPrewitt_y_abs = cv2.convertScaleAbs(imgPrewitt_y)
imgPrewitt = cv2.addWeighted(imgPrewitt_x_abs, 0.5, imgPrewitt_y_abs, 0.5, 0)
return imgPrewitt
@Slot()
@process(isGray=True)
def laplacian(self, src):
img = src
# kernel_Laplacian_K1 = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]])
# imgLaplacian = cv2.filter2D(img, -1, kernel_Laplacian_K1)
imgLaplacian = cv2.Laplacian(img, cv2.CV_16S, ksize=3)
imgLaplacian_abs = cv2.convertScaleAbs(imgLaplacian)
return imgLaplacian_abs
@Slot(int, float)
@process(isGray=True)
def loG(self, size, sigma, src):
img = src
imgGaussBlur = cv2.GaussianBlur(img, (size, size), sigma)
imgLoG = cv2.Laplacian(imgGaussBlur, cv2.CV_16S, ksize=size)
imgLoG_abs = cv2.convertScaleAbs(imgLoG)
return imgLoG_abs
@Slot(int, int, int)
@process(isGray=True)
def canny(self, size, low_thresh, high_thresh, src):
img = src
kSize = (size, size)
imgGauss = cv2.GaussianBlur(img, kSize, sigmaX=1.0) # sigma=1.0
sobel_x = cv2.Sobel(imgGauss, cv2.CV_16S, 1, 0)
sobel_y = cv2.Sobel(imgGauss, cv2.CV_16S, 0, 1)
imgCanny = cv2.Canny(sobel_x, sobel_y, low_thresh, high_thresh)
return imgCanny
# 图像平滑
@Slot(int)
@process()
def blur(self, size, src):
img = src
imgBlur = cv2.blur(img, (size, size))
return imgBlur
@Slot(int)
@process()
def medianBlur(self, size, src):
# 1.73:图像的非线性滤波 (中值滤波器)
img = src
imgMedianBlur = cv2.medianBlur(img, size)
return imgMedianBlur
@Slot(int)
@process(isGray=True)
def lowPassIdealBlur(self, D0, src):
img = src
height, width = img.shape[:2]
centerX, centerY = int(height / 2), int(width / 2)
x, y = np.mgrid[0:height, 0:width]
D = np.sqrt(np.power(x - centerX, 2) + np.power(y - centerY, 2))
H = np.where(D > D0, 0, 1)
fft = np.fft.fft2(img) # 傅里叶变换
fft_shift = np.fft.fftshift(fft) # 中心化
fft_mask = fft_shift * H
ifft_shift = np.fft.ifftshift(fft_mask)
ifft = np.fft.ifft2(ifft_shift)
ifft_abs = np.abs(ifft)
return cv2.convertScaleAbs(ifft_abs)
@Slot(int, int)
@process(isGray=True)
def lowPassButterworthBlur(self, D0, n, src):
img = src
height, width = img.shape[:2]
centerX, centerY = int(height / 2), int(width / 2)
x, y = np.mgrid[0:height, 0:width]
D = np.sqrt(np.power(x - centerX, 2) + np.power(y - centerY, 2))
H = 1.0 / (1.0 + np.power(D / D0, 2 * n))
fft = np.fft.fft2(img) # 傅里叶变换
fft_shift = np.fft.fftshift(fft) # 中心化
fft_mask = fft_shift * H
ifft_shift = np.fft.ifftshift(fft_mask)
ifft = np.fft.ifft2(ifft_shift)
ifft_abs = np.abs(ifft)
return cv2.convertScaleAbs(ifft_abs)
@Slot(int)
@process(isGray=True)
def lowPassGaussianBlur(self, D0, src):
img = src
height, width = img.shape[:2]
centerX, centerY = int(height / 2), int(width / 2)
x, y = np.mgrid[0:height, 0:width]
D = np.sqrt(np.power(x - centerX, 2) + np.power(y - centerY, 2))
H = np.exp(-1 * np.power(D, 2) / (2 * np.power(D0, 2)))
fft = np.fft.fft2(img) # 傅里叶变换
fft_shift = np.fft.fftshift(fft) # 中心化
fft_mask = fft_shift * H
ifft_shift = np.fft.ifftshift(fft_mask)
ifft = np.fft.ifft2(ifft_shift)
ifft_abs = np.abs(ifft)
return cv2.convertScaleAbs(ifft_abs)
# 形态处理
@Slot(int, int)
@process()
def erode(self, shape, size, src):
# 读取原始图像
img = src
kernel = cv2.getStructuringElement(shape, (size, size))
imgErode = cv2.erode(img, kernel=kernel) # 图像腐蚀
return imgErode
@Slot(int, int)
@process()
def dilate(self, shape, size, src):
# 读取原始图像
img = src
kernel = cv2.getStructuringElement(shape, (size, size))
imgDilate = cv2.dilate(img, kernel=kernel) # 图像膨胀
return imgDilate
@Slot(int, int)
@process()
def open(self, shape, size, src):
# 读取原始图像
img = src
kernel = cv2.getStructuringElement(shape, (size, size))
imgOpen = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
return imgOpen
@Slot(int, int)
@process()
def close(self, shape, size, src):
# 读取原始图像
img = src
kernel = cv2.getStructuringElement(shape, (size, size))
imgClose = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
return imgClose
# 噪声添加
@Slot(float, float)
@process()
def gaussNoise(self, mu, sigma, src):
img = src
noiseGauss = np.random.normal(mu, sigma, img.shape)
imgGaussNoise = img + noiseGauss
return cv2.convertScaleAbs(imgGaussNoise)
@Slot(float)
@process()
def expNoise(self, a, src):
# 指数噪声 (Exponential noise)
img = src
noiseExponent = np.random.exponential(scale=a, size=img.shape)
imgExponentNoise = img + noiseExponent
return cv2.convertScaleAbs(imgExponentNoise)
@Slot(float, float)
@process()
def uniformNoise(self, mean, sigma, src):
img = src
a = 2 * mean - np.sqrt(12 * sigma)
b = 2 * mean + np.sqrt(12 * sigma)
noiseUniform = np.random.uniform(a, b, img.shape)
imgUniformNoise = img + noiseUniform
return cv2.convertScaleAbs(imgUniformNoise)
@Slot(float, float)
@process()
def pepperSaltNoise(self, ps, pp, src):
img = src
mask = np.random.choice((0, 0.5, 1), size=img.shape[:2], p=[pp, (1 - ps - pp), ps])
imgChoiceNoise = img.copy()
imgChoiceNoise[mask == 1] = 255
imgChoiceNoise[mask == 0] = 0
return imgChoiceNoise
# 风格迁移
@Slot(str)
@process()
def styleTransfer(self, style, src):
predictor = LapStylePredictor(style)
img = predictor.run(src)
return img
if __name__ == "__main__":
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())