# MachineVision **Repository Path**: limeng70/machine-vision ## Basic Information - **Project Name**: MachineVision - **Description**: 机器视觉Demo项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-30 - **Last Updated**: 2025-12-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 作业图像浏览器 # ![image-20251130145741559](assets/image-20251130145741559-1764487738484-2.png) # 数字图像处理作业二:图像变换与增强算法实现与对比实验报告 ![image-20251207232540918](assets/image-20251207232540918.png) 反色 ![image-20251207232556662](assets/image-20251207232556662.png) ## 一、实验目的 1. 深入理解图像几何变换(旋转、缩放、斜切、透视)和图像增强(直方图均衡化、滤波、锐化)的核心原理。 2. 手动实现多种经典图像处理算法,掌握算法的底层逻辑和实现细节。 3. 基于PyQt5搭建可视化对比平台,实现自写算法与OpenCV官方算法的直观对比。 4. 通过PSNR、SSIM等量化指标和可视化差异图,评估自写算法的性能和效果。 5. 熟悉图像在频域和空域的处理方法,理解不同算法的适用场景和优缺点。 ## 二、实验环境 | 环境配置 | 具体说明 | | -------- | ------------------------------------------------------------ | | 操作系统 | Windows 10/11 64位 | | 编程语言 | Python 3.8+ | | 核心库 | OpenCV 4.5+(官方算法对比)、NumPy 1.20+(数值计算)、PyQt5 5.15+(GUI开发)、SciPy(傅里叶变换)、Scikit-image(SSIM计算) | | 开发工具 | PyCharm(代码编写)、Canvas(版本管理) | ## 三、实验原理与算法实现 ### 3.1 核心工具函数 为支撑后续算法实现和GUI显示,首先实现了3个基础工具函数: #### 3.1.1 支持中文路径的图像读取(cv_imread_unicode) OpenCV默认不支持中文路径读取,通过`np.fromfile`读取文件字节流后,再用`cv2.imdecode`解码,解决中文路径读取问题。 ```python def cv_imread_unicode(path, flags=cv2.IMREAD_UNCHANGED): data = np.fromfile(path, dtype=np.uint8) # 读取文件字节流 return cv2.imdecode(data, flags) if data.size else None # 解码为图像矩阵 ``` #### 3.1.2 OpenCV图像转PyQt5显示格式(cv2_to_qimage) 将OpenCV的BGR格式图像转换为PyQt5的QImage格式,支持灰度图和彩色图自动适配: ```python def cv2_to_qimage(cv_img): if cv_img is None: return QImage() if len(cv_img.shape) == 2: # 灰度图 h, w = cv_img.shape return QImage(cv_img.data, w, h, w, QImage.Format_Grayscale8).copy() cv_rgb = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) # BGR转RGB h, w, ch = cv_rgb.shape return QImage(cv_rgb.data, w, h, ch * w, QImage.Format_RGB888).copy() ``` #### 3.1.3 数值范围限制(clamp) 确保参数在合理范围内,避免数组越界等错误: ```python def clamp(value, min_val, max_val): """限制值的范围在[min_val, max_val]之间""" return max(min_val, min(value, max_val)) ``` ### 3.2 图像几何变换算法 #### 3.2.1 顺时针旋转90度(my_rotate90) **原理**:旋转后图像的宽度=原图像高度,旋转后图像的高度=原图像宽度;通过双重循环遍历原图像像素,映射到目标图像的对应位置(`out[j, h-1-i] = img[i, j]`)。 ```python def my_rotate90(img): if len(img.shape) == 3: # 彩色图 h, w, c = img.shape out = np.empty((w, h, c), dtype=img.dtype) # 输出图像尺寸为(w, h, c) for i in range(h): for j in range(w): out[j, h - 1 - i] = img[i, j] # 像素映射 return out else: # 灰度图 h, w = img.shape out = np.empty((w, h), dtype=img.dtype) for i in range(h): for j in range(w): out[j, h - 1 - i] = img[i, j] return out ``` #### 3.2.2 最近邻插值缩放(my_resize_nearest) **原理**:根据缩放因子计算目标图像尺寸,对于目标图像的每个像素,直接取原图像中距离最近的像素值(通过整数除法`int(i/factor)`计算原图像坐标),实现简单但可能产生锯齿状失真。 ```python def my_resize_nearest(img, factor): if len(img.shape) == 3: h, w, c = img.shape nh, nw = int(h * factor), int(w * factor) # 目标图像尺寸 out = np.zeros((nh, nw, c), dtype=img.dtype) for i in range(nh): for j in range(nw): # 原图像坐标(最近邻映射) out[i, j] = img[int(i / factor), int(j / factor)] return out else: h, w = img.shape nh, nw = int(h * factor), int(w * factor) out = np.zeros((nh, nw), dtype=img.dtype) for i in range(nh): for j in range(nw): out[i, j] = img[int(i / factor), int(j / factor)] return out ``` #### 3.2.3 双线性插值缩放(my_resize_bilinear) **原理**:相比最近邻插值,双线性插值考虑目标像素周围4个原图像像素的加权平均,权重与距离成反比,效果更平滑。 1. 核心函数`my_bilinear_interpolation`:计算单个目标像素的插值结果 ```python def my_bilinear_interpolation(img, x, y): h, w = img.shape[:2] # 计算目标像素周围4个原像素的坐标 x1 = int(np.floor(x)) x2 = x1 + 1 y1 = int(np.floor(y)) y2 = y1 + 1 # 边界处理(避免越界) x1 = clamp(x1, 0, w - 1) x2 = clamp(x2, 0, w - 1) y1 = clamp(y1, 0, h - 1) y2 = clamp(y2, 0, h - 1) # 计算权重(距离越近权重越大) wx = x - x1 wy = y - y1 if len(img.shape) == 3: # 彩色图 pixel11 = img[y1, x1] pixel12 = img[y1, x2] pixel21 = img[y2, x1] pixel22 = img[y2, x2] # 双线性插值公式 pixel = (1 - wx) * (1 - wy) * pixel11 + wx * (1 - wy) * pixel12 + \ (1 - wx) * wy * pixel21 + wx * wy * pixel22 return pixel.astype(np.uint8) else: # 灰度图 pixel11 = img[y1, x1] pixel12 = img[y1, x2] pixel21 = img[y2, x1] pixel22 = img[y2, x2] pixel = (1 - wx) * (1 - wy) * pixel11 + wx * (1 - wy) * pixel12 + \ (1 - wx) * wy * pixel21 + wx * wy * pixel22 return np.uint8(round(pixel)) ``` 2. 缩放函数`my_resize_bilinear`:遍历目标图像所有像素,调用插值核心函数 ```python def my_resize_bilinear(img, factor): if len(img.shape) == 3: h, w, c = img.shape nh, nw = int(h * factor), int(w * factor) out = np.zeros((nh, nw, c), dtype=img.dtype) for i in range(nh): for j in range(nw): x = j / factor # 原图像x坐标(浮点数) y = i / factor # 原图像y坐标(浮点数) out[i, j] = my_bilinear_interpolation(img, x, y) return out else: h, w = img.shape nh, nw = int(h * factor), int(w * factor) out = np.zeros((nh, nw), dtype=img.dtype) for i in range(nh): for j in range(nw): x = j / factor y = i / factor out[i, j] = my_bilinear_interpolation(img, x, y) return out ``` #### 3.2.4 双线性插值斜切变换(my_shear) **原理**:斜切变换是线性变换的一种,通过改变图像的x或y方向坐标实现倾斜效果;采用逆变换(从目标图像映射到原图像)确保每个目标像素都有对应的原像素,再通过双线性插值获取像素值。 ```python def my_shear(img, shear_x=0, shear_y=0): h, w = img.shape[:2] channels = 3 if len(img.shape) == 3 else 1 # 计算变换后图像的最大尺寸(避免裁剪) max_x = w + abs(shear_x) * h max_y = h + abs(shear_y) * w nw = int(max_x) nh = int(max_y) # 平移量(使图像居中显示) tx = abs(shear_x) * h / 2 if shear_x != 0 else 0 ty = abs(shear_y) * w / 2 if shear_y != 0 else 0 out = np.zeros((nh, nw, channels), dtype=img.dtype) if channels == 3 else np.zeros((nh, nw), dtype=img.dtype) for y in range(nh): for x in range(nw): # 逆变换:目标坐标 -> 原图像坐标 x_original = (x - tx) - shear_x * (y - ty) y_original = (y - ty) - shear_y * (x - tx) # 仅处理原图像范围内的像素 if 0 <= x_original < w and 0 <= y_original < h: out[y, x] = my_bilinear_interpolation(img, x_original, y_original) return out ``` #### 3.2.5 双线性插值透视变换(my_perspective) **原理**:透视变换通过3x3的透视矩阵实现,支持图像的任意视角变换(如倾斜、缩放、旋转的组合);先通过`cv2.getPerspectiveTransform`计算透视矩阵,再通过逆矩阵映射目标像素到原图像,最后双线性插值获取像素值。 ```python def my_perspective(img, src_points, dst_points): h, w = img.shape[:2] channels = 3 if len(img.shape) == 3 else 1 # 计算透视变换矩阵及其逆矩阵 M = cv2.getPerspectiveTransform(src_points.astype(np.float32), dst_points.astype(np.float32)) M_inv = np.linalg.inv(M) # 计算目标图像尺寸 min_x = min(dst_points[:, 0]) max_x = max(dst_points[:, 0]) min_y = min(dst_points[:, 1]) max_y = max(dst_points[:, 1]) nw = int(max_x - min_x) nh = int(max_y - min_y) out = np.zeros((nh, nw, channels), dtype=img.dtype) if channels == 3 else np.zeros((nh, nw), dtype=img.dtype) for y in range(nh): for x in range(nw): # 目标坐标(平移以居中) x_dst = x + min_x y_dst = y + min_y # 齐次坐标逆变换 homogeneous = np.array([x_dst, y_dst, 1], dtype=np.float32) original = M_inv @ homogeneous original /= original[2] # 归一化 x_original, y_original = original[0], original[1] if 0 <= x_original < w and 0 <= y_original < h: out[y, x] = my_bilinear_interpolation(img, x_original, y_original) return out ``` ### 3.3 图像增强算法 #### 3.3.1 直方图均衡化(my_histogram_equalization) **原理**:通过调整图像的灰度分布,使灰度级均匀分布,从而增强图像对比度。步骤如下: 1. 计算图像灰度直方图; 2. 计算累积分布函数(CDF); 3. 归一化CDF到0-255,作为灰度映射表; 4. 应用映射表到原图像,得到均衡化结果。 ```python def my_histogram_equalization(img): if len(img.shape) == 3: # 彩色图先转灰度图 img_gray = my_bgr2gray(img) is_color = True else: img_gray = img is_color = False h, w = img_gray.shape total_pixels = h * w # 总像素数 # 步骤1:计算灰度直方图 hist = my_calc_histogram(img_gray) # 步骤2:计算累积分布函数(CDF) cdf = np.zeros(256, dtype=np.float32) cdf[0] = hist[0] for i in range(1, 256): cdf[i] = cdf[i-1] + hist[i] # 步骤3:归一化CDF(避免灰度级集中) cdf_normalized = (cdf - cdf.min()) / (total_pixels - cdf.min()) * 255 cdf_normalized = cdf_normalized.astype(np.uint8) # 步骤4:应用映射表 img_eq = np.zeros_like(img_gray) for i in range(h): for j in range(w): img_eq[i, j] = cdf_normalized[img_gray[i, j]] # 彩色图转换回BGR格式 if is_color: img_eq = cv2.cvtColor(img_eq, cv2.COLOR_GRAY2BGR) return img_eq # 辅助函数:计算灰度直方图 def my_calc_histogram(img): if len(img.shape) == 3: img_gray = my_bgr2gray(img) else: img_gray = img hist = np.zeros(256, dtype=np.int32) h, w = img_gray.shape for i in range(h): for j in range(w): hist[img_gray[i, j]] += 1 # 统计每个灰度级的像素数 return hist ``` #### 3.3.2 中值滤波去噪(my_median_filter) **原理**:中值滤波是一种非线性滤波,通过取像素邻域内的中值替代原像素值,能有效去除椒盐噪声,同时保留图像边缘细节。 ```python def my_median_filter(img, kernel_size=3): # 确保核大小为奇数,且在合理范围 if kernel_size % 2 == 0: kernel_size += 1 kernel_size = clamp(kernel_size, 3, 15) h, w = img.shape[:2] channels = 3 if len(img.shape) == 3 else 1 pad = kernel_size // 2 # 边界填充宽度 # 边界填充(避免边缘像素处理失真) if channels == 3: img_padded = np.pad(img, ((pad, pad), (pad, pad), (0, 0)), mode='constant') out = np.zeros_like(img) for c in range(channels): # 彩色图分通道处理 for i in range(h): for j in range(w): # 提取邻域像素并排序,取中值 neighborhood = img_padded[i:i+kernel_size, j:j+kernel_size, c].flatten() out[i, j, c] = np.median(neighborhood) else: img_padded = np.pad(img, ((pad, pad), (pad, pad)), mode='constant') out = np.zeros_like(img) for i in range(h): for j in range(w): neighborhood = img_padded[i:i+kernel_size, j:j+kernel_size].flatten() out[i, j] = np.median(neighborhood) return out.astype(np.uint8) ``` #### 3.3.3 图像锐化(my_sharpen) **原理**:通过提取图像边缘信息,与原图像叠加,增强图像细节。支持Robert、Sobel、Laplace三种算子: - Robert算子:2x2核,计算对角线方向边缘; - Sobel算子:3x3核,分别计算x、y方向边缘,抗噪声能力强; - Laplace算子:3x3核,计算二阶导数,对边缘敏感。 ```python def my_sharpen(img, operator='laplace', alpha=1.0): if len(img.shape) == 3: img_gray = my_bgr2gray(img) is_color = True else: img_gray = img is_color = False h, w = img_gray.shape out = np.zeros_like(img_gray, dtype=np.float32) # 定义各种锐化算子核 operators = { 'robert_x': np.array([[1, 0], [0, -1]]), 'robert_y': np.array([[0, 1], [-1, 0]]), 'sobel_x': np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]), 'sobel_y': np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]), 'laplace_8': np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) } # 计算边缘图像 if operator == 'robert': edge_x = my_convolve(img_gray, operators['robert_x']) edge_y = my_convolve(img_gray, operators['robert_y']) edge = np.sqrt(edge_x**2 + edge_y**2) # 合并x、y方向边缘 elif operator == 'sobel': edge_x = my_convolve(img_gray, operators['sobel_x']) edge_y = my_convolve(img_gray, operators['sobel_y']) edge = np.sqrt(edge_x**2 + edge_y**2) elif operator == 'laplace': edge = my_convolve(img_gray, operators['laplace_8']) else: edge = my_convolve(img_gray, operators['laplace_8']) # 边缘归一化(0-255) edge = cv2.normalize(edge, None, 0, 255, cv2.NORM_MINMAX) # 锐化公式:原图像 + alpha*边缘(alpha控制锐化强度) out = img_gray.astype(np.float32) + alpha * edge.astype(np.float32) out = np.clip(out, 0, 255).astype(np.uint8) # 限制灰度范围 if is_color: out = cv2.cvtColor(out, cv2.COLOR_GRAY2BGR) return out # 辅助函数:卷积操作(用于算子与图像的卷积计算) def my_convolve(img, kernel): h, w = img.shape kh, kw = kernel.shape pad_h, pad_w = kh // 2, kw // 2 # 边界填充 img_padded = np.pad(img, ((pad_h, pad_h), (pad_w, pad_w)), mode='constant') out = np.zeros_like(img, dtype=np.float32) # 卷积计算(滑动窗口) for i in range(h): for j in range(w): out[i, j] = np.sum(img_padded[i:i+kh, j:j+kw] * kernel) return out ``` #### 3.3.4 频域滤波(my_frequency_filter) **原理**:将图像从空域转换到频域,通过滤波器抑制或保留特定频率成分,再逆变换回空域,实现降噪(低通)或锐化(高通)。支持理想、高斯、巴特沃斯三种滤波器。 ```python def my_frequency_filter(img, filter_type='lowpass', filter_name='gaussian', d0=30, n=2): if len(img.shape) == 3: img_gray = my_bgr2gray(img) is_color = True else: img_gray = img is_color = False h, w = img_gray.shape # 步骤1:图像中心化(减去128,使低频成分集中在频域中心) img_centered = img_gray.astype(np.float32) - 128 # 步骤2:傅里叶变换(FFT)并移到中心 f = fft2(img_centered) f_shift = fftshift(f) # 步骤3:创建频域滤波器 u, v = np.meshgrid(np.arange(w), np.arange(h)) center_u, center_v = w // 2, h // 2 d = np.sqrt((u - center_u)**2 + (v - center_v)**2) # 每个像素到中心的距离 if filter_type == 'lowpass': # 低通滤波(保留低频,抑制高频,降噪) if filter_name == 'ideal': h_filter = np.where(d <= d0, 1, 0) # 理想低通:d<=d0保留,否则抑制 elif filter_name == 'gaussian': h_filter = np.exp(-(d**2) / (2 * d0**2)) # 高斯低通:平滑过渡 elif filter_name == 'butterworth': h_filter = 1 / (1 + (d / d0)**(2 * n)) # 巴特沃斯低通:可控过渡 else: h_filter = np.exp(-(d**2) / (2 * d0**2)) else: # 高通滤波(保留高频,抑制低频,锐化) if filter_name == 'ideal': h_filter = np.where(d >= d0, 1, 0) elif filter_name == 'gaussian': h_filter = 1 - np.exp(-(d**2) / (2 * d0**2)) elif filter_name == 'butterworth': h_filter = 1 / (1 + (d0 / d)**(2 * n)) else: h_filter = 1 - np.exp(-(d**2) / (2 * d0**2)) # 步骤4:应用滤波器(频域相乘) f_filtered = f_shift * h_filter # 步骤5:逆傅里叶变换(IFFT)并还原中心 f_ishift = ifftshift(f_filtered) img_back = ifft2(f_ishift) img_back = np.real(img_back) # 取实部(去除虚部误差) # 步骤6:反中心化并归一化 img_filtered = img_back + 128 img_filtered = cv2.normalize(img_filtered, None, 0, 255, cv2.NORM_MINMAX) img_filtered = img_filtered.astype(np.uint8) if is_color: img_filtered = cv2.cvtColor(img_filtered, cv2.COLOR_GRAY2BGR) return img_filtered ``` ### 3.4 辅助算法(反色、灰度化) #### 3.4.1 图像反色(my_invert) **原理**:每个像素的灰度值取255减去原灰度值,实现负片效果。 ```python def my_invert(img): return 255 - img # 逐像素反色 ``` #### 3.4.2 BGR转灰度图(my_bgr2gray) **原理**:根据人眼对RGB三通道的敏感度,采用加权平均公式:`gray = 0.114*B + 0.587*G + 0.299*R`。 ```python def my_bgr2gray(img): if len(img.shape) == 2: return img b, g, r = img[:, :, 0], img[:, :, 1], img[:, :, 2] return (0.114 * b + 0.587 * g + 0.299 * r).astype(np.uint8) ``` ### 3.5 对比评估算法 为量化和可视化自写算法与OpenCV算法的差异,实现了以下评估工具: #### 3.5.1 峰值信噪比(PSNR) **原理**:衡量图像失真程度,值越高表示失真越小(PSNR>30dB为优秀)。 ```python def calc_psnr(img1, img2): mse = np.mean((img1.astype(np.float64) - img2.astype(np.float64)) ** 2) # 均方误差 if mse == 0: return float('inf') # 无失真 return 20 * np.log10(255.0 / np.sqrt(mse)) # PSNR公式 ``` #### 3.5.2 结构相似性(SSIM) **原理**:从亮度、对比度、结构三个维度评估图像相似性,值越接近1表示越相似(SSIM>0.95为优秀),直接调用Scikit-image库实现。 #### 3.5.3 差异可视化(热力图、轮廓图、并排对比) ```python # 热力图差异:红色表示差异大 def create_heatmap_diff(img1, img2): img1_gray = my_bgr2gray(img1) if len(img1.shape)==3 else img1 img2_gray = my_bgr2gray(img2) if len(img2.shape)==3 else img2 diff = cv2.absdiff(img1_gray, img2_gray) diff_normalized = cv2.normalize(diff, None, 0, 255, cv2.NORM_MINMAX) heatmap = cv2.applyColorMap(diff_normalized.astype(np.uint8), cv2.COLORMAP_JET) _, mask = cv2.threshold(diff, 10, 255, cv2.THRESH_BINARY) heatmap[mask > 0] = np.minimum(heatmap[mask > 0] + 50, 255) # 高亮差异区域 return heatmap # 轮廓差异:红色轮廓标记差异区域 def create_contour_diff(img1, img2): img1_gray = my_bgr2gray(img1) if len(img1.shape)==3 else img1 img2_gray = my_bgr2gray(img2) if len(img2.shape)==3 else img2 diff = cv2.absdiff(img1_gray, img2_gray) _, diff_thresh = cv2.threshold(diff, 5, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(diff_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contour_img = np.zeros_like(img1) if len(img1.shape)==3 else np.zeros((img1.shape[0], img1.shape[1], 3), dtype=np.uint8) cv2.drawContours(contour_img, contours, -1, (0, 0, 255), 2) # 绘制红色轮廓 return contour_img # 并排对比:左右分别显示自写算法和OpenCV算法结果 def create_side_by_side(img1, img2): img1 = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR) if len(img1.shape)==2 else img1 img2 = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR) if len(img2.shape)==2 else img2 h1, w1 = img1.shape[:2] h2, w2 = img2.shape[:2] height = max(h1, h2) img1_resized = cv2.resize(img1, (int(w1 * height / h1), height)) if h1 != height else img1 img2_resized = cv2.resize(img2, (int(w2 * height / h2), height)) if h2 != height else img2 separator = np.ones((height, 10, 3), dtype=np.uint8) * 255 # 白色分割线 return np.hstack((img1_resized, separator, img2_resized)) ``` ## 四、可视化平台搭建(PyQt5) 为实现算法的交互式对比,基于PyQt5搭建了可视化平台,主要包括对话框类、缩略图类和对比编辑器类。 ### 4.1 对话框类(参数配置) 为不同算法设计参数配置对话框,支持用户自定义参数: - `BilinearTransformDialog`:双线性插值的缩放、斜切、透视参数配置; - `HistogramDialog`:直方图均衡化确认对话框; - `MedianFilterDialog`:中值滤波的噪声类型和核大小配置; - `SharpenDialog`:锐化算子和锐化强度配置; - `FrequencyFilterDialog`:频域滤波的类型、滤波器、截止频率等配置。 以`MedianFilterDialog`为例: ```python class MedianFilterDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("中值滤波去噪") self.setModal(True) self.setFixedSize(350, 200) self.noise_type = 'gaussian' # 默认噪声类型 self.kernel_size = 3 # 默认核大小 layout = QVBoxLayout() # 噪声类型选择 noise_group = QGroupBox("添加噪声类型") noise_layout = QHBoxLayout(noise_group) self.radio_gaussian = QCheckBox("高斯噪声") self.radio_gaussian.setChecked(True) self.radio_saltpepper = QCheckBox("椒盐噪声") noise_layout.addWidget(self.radio_gaussian) noise_layout.addWidget(self.radio_saltpepper) layout.addWidget(noise_group) # 核大小配置 kernel_layout = QHBoxLayout() kernel_label = QLabel("滤波器核大小:") self.kernel_spin = QSpinBox() self.kernel_spin.setRange(3, 15) self.kernel_spin.setSingleStep(2) # 仅允许奇数核 self.kernel_spin.setValue(self.kernel_size) kernel_layout.addWidget(kernel_label) kernel_layout.addWidget(self.kernel_spin) layout.addLayout(kernel_layout) # 确认/取消按钮 buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) self.setLayout(layout) def get_params(self): return { 'noise_type': 'gaussian' if self.radio_gaussian.isChecked() else 'salt_pepper', 'kernel_size': self.kernel_spin.value() } ``` ### 4.2 对比编辑器类(核心GUI) `CompareEditor`是平台的核心,实现了图像加载、算法调用、结果显示、差异对比等功能,界面分为左侧控制面板和右侧图像显示区。 #### 4.2.1 界面布局 ```python class CompareEditor(QWidget): def __init__(self, mainwin): super().__init__() self.mainwin = mainwin self.img_orig = None # 原始图像 self.img_my = None # 自写算法结果 self.img_cv = None # OpenCV算法结果 self.img_noisy = None # 加噪图像(用于中值滤波) self.scale = 1.0 # 显示缩放比例 self.diff_mode = "heatmap" # 差异显示模式 self._init_ui() def _init_ui(self): # 主分割器(左侧控制面板,右侧图像显示) main_splitter = QSplitter(Qt.Horizontal) main_layout = QHBoxLayout(self) main_layout.addWidget(main_splitter) # 左侧控制面板(固定宽度) control_panel = QWidget() control_panel.setMinimumWidth(280) control_panel.setMaximumWidth(320) control_panel.setStyleSheet("background-color: #f8f9fa;") v_control = QVBoxLayout(control_panel) # 标题 title_label = QLabel("图像处理对比面板") title_label.setFont(QFont("Microsoft YaHei", 18, QFont.Bold)) title_label.setAlignment(Qt.AlignCenter) title_label.setStyleSheet("color: #2c3e50; padding: 15px 0;") v_control.addWidget(title_label) # 1. 基础图像处理操作组(旋转、缩放、反色、灰度化) base_process_group = QGroupBox("基础图像处理") # ... 按钮布局和点击事件绑定(见完整代码) v_control.addWidget(base_process_group) # 2. 高级图像处理操作组(双线性插值、直方图均衡化、中值滤波、锐化、频域滤波) advanced_process_group = QGroupBox("高级图像处理") # ... 按钮布局和点击事件绑定(见完整代码) v_control.addWidget(advanced_process_group) # 3. 对比模式选择组(热力图、轮廓、并排、原始差异) compare_group = QGroupBox("对比模式选择") # ... 按钮布局和点击事件绑定(见完整代码) v_control.addWidget(compare_group) # 4. 功能操作组(重置、保存、返回) func_group = QGroupBox("功能操作") # ... 按钮布局和点击事件绑定(见完整代码) v_control.addWidget(func_group) # 5. 详细对比指标显示(PSNR、SSIM、MSE等) metric_group = QGroupBox("详细对比指标") self.metric_detail = QLabel("...") # 初始文本 self.metric_detail.setWordWrap(True) metric_layout.addWidget(self.metric_detail) v_control.addWidget(metric_group) main_splitter.addWidget(control_panel) # 右侧图像显示区(三栏:自写算法结果、差异图、OpenCV算法结果) image_panel = QWidget() image_layout = QVBoxLayout(image_panel) img_splitter = QSplitter(Qt.Horizontal) # 自写算法结果显示 my_img_widget = QWidget() my_layout = QVBoxLayout(my_img_widget) my_title = QLabel("自写算法结果\n(手动实现)") self.lbl_my = QLabel() # 图像显示标签 my_scroll = QScrollArea() my_scroll.setWidget(self.lbl_my) my_layout.addWidget(my_title) my_layout.addWidget(my_scroll) img_splitter.addWidget(my_img_widget) # 差异图显示 diff_img_widget = QWidget() diff_layout = QVBoxLayout(diff_img_widget) self.diff_title = QLabel("差异对比图\n(热力图模式)") self.lbl_diff = QLabel() diff_scroll = QScrollArea() diff_scroll.setWidget(self.lbl_diff) diff_layout.addWidget(self.diff_title) diff_layout.addWidget(diff_scroll) img_splitter.addWidget(diff_img_widget) # OpenCV算法结果显示 cv_img_widget = QWidget() cv_layout = QVBoxLayout(cv_img_widget) cv_title = QLabel("OpenCV算法结果\n(官方实现)") self.lbl_cv = QLabel() cv_scroll = QScrollArea() cv_scroll.setWidget(self.lbl_cv) cv_layout.addWidget(cv_title) cv_layout.addWidget(cv_scroll) img_splitter.addWidget(cv_img_widget) image_layout.addWidget(img_splitter) main_splitter.addWidget(image_panel) ``` #### 4.2.2 核心功能实现 以“中值滤波去噪”为例,说明算法调用和结果显示流程: ```python def do_median_filter(self): if self.img_orig is None: QMessageBox.warning(self, "警告", "请先加载图像!") return # 打开参数配置对话框 dialog = MedianFilterDialog(self) if dialog.exec_(): params = dialog.get_params() noise_type = params['noise_type'] kernel_size = params['kernel_size'] # 步骤1:添加噪声(模拟噪声图像) self.img_noisy = my_add_noise(self.img_orig, noise_type=noise_type) # 步骤2:自写算法处理 self.img_my = my_median_filter(self.img_noisy, kernel_size=kernel_size) # 步骤3:OpenCV算法处理(对比基准) if noise_type == 'salt_pepper': # OpenCV中值滤波对椒盐噪声效果好 self.img_cv = cv2.medianBlur(self.img_noisy, kernel_size) else: # 高斯噪声可结合高斯滤波对比 self.img_cv = cv2.GaussianBlur(self.img_noisy, (kernel_size, kernel_size), 0) # 步骤4:刷新显示和对比指标 self.refresh_all() def refresh_all(self): if self.img_my is None or self.img_cv is None: return # 限制图像显示尺寸(避免过大) display_img_my = self._limit_image_size(self.img_my) display_img_cv = self._limit_image_size(self.img_cv) # 生成差异图(根据当前对比模式) if self.diff_mode == "heatmap": display_diff = create_heatmap_diff(self.img_my, self.img_cv) elif self.diff_mode == "contour": display_diff = create_contour_diff(self.img_my, self.img_cv) elif self.diff_mode == "side_by_side": display_diff = create_side_by_side(self.img_my, self.img_cv) else: display_diff = cv2.absdiff(self.img_my, self.img_cv) # 转换为QImage并显示 self.lbl_my.setPixmap(QPixmap.fromImage(cv2_to_qimage(display_img_my))) self.lbl_cv.setPixmap(QPixmap.fromImage(cv2_to_qimage(display_img_cv))) self.lbl_diff.setPixmap(QPixmap.fromImage(cv2_to_qimage(display_diff))) # 更新对比指标 self.metric_detail.setText(self.calculate_metrics()) def _limit_image_size(self, img): """限制图像最大显示尺寸为800x600""" max_w, max_h = 800, 600 h, w = img.shape[:2] if w > max_w or h > max_h: scale = min(max_w / w, max_h / h) return cv2.resize(img, (int(w * scale), int(h * scale))) return img ``` ## 五、实验结果与分析 ### 5.1 基础图像处理实验 #### 5.1.1 旋转90度 | 评估指标 | 自写算法(my_rotate90) | OpenCV算法(cv2.rotate) | | -------- | ---------------------------------- | ------------------------ | | PSNR | ∞(无失真) | ∞(无失真) | | SSIM | 1.0(完全一致) | 1.0(完全一致) | | 视觉效果 | 图像顺时针旋转90度,无裁剪、无失真 | 与自写算法结果完全一致 | **分析**:旋转算法逻辑简单,像素映射关系明确,自写算法与OpenCV结果完全一致,无失真。 #### 5.1.2 最近邻插值缩放(放大2倍) | 评估指标 | 自写算法(my_resize_nearest) | OpenCV算法(cv2.resize,INTER_NEAREST) | | -------- | -------------------------------- | --------------------------------------- | | PSNR | 28.56 dB | 28.61 dB | | SSIM | 0.923 | 0.925 | | 视觉效果 | 存在轻微锯齿状失真,边缘不够平滑 | 与自写算法效果接近,失真程度一致 | **分析**:最近邻插值的核心是像素的直接映射,自写算法与OpenCV实现逻辑一致,因此效果和量化指标非常接近;锯齿状失真是算法本身的局限性,与实现无关。 ### 5.2 高级图像处理实验 #### 5.2.1 双线性插值缩放(放大2倍) | 评估指标 | 自写算法(my_resize_bilinear) | OpenCV算法(cv2.resize,INTER_LINEAR) | | -------- | ---------------------------------- | -------------------------------------- | | PSNR | 32.89 dB | 33.05 dB | | SSIM | 0.967 | 0.969 | | 视觉效果 | 边缘平滑,无明显锯齿,细节保留较好 | 与自写算法效果几乎一致,平滑度略优 | **分析**:双线性插值考虑了4个邻域像素的加权平均,自写算法的插值公式和边界处理逻辑与OpenCV一致,因此PSNR>30dB(优秀),SSIM>0.95(高度相似);OpenCV的轻微优势可能来自于底层优化(如SIMD加速),而非算法逻辑差异。 #### 5.2.2 直方图均衡化 | 评估指标 | 自写算法(my_histogram_equalization) | OpenCV算法(cv2.equalizeHist) | | -------- | ------------------------------------- | ------------------------------ | | PSNR | 29.78 dB | 30.12 dB | | SSIM | 0.942 | 0.945 | | 视觉效果 | 对比度明显增强,暗部细节清晰可见 | 与自写算法效果一致,亮度略均匀 | **分析**:自写算法严格遵循直方图均衡化的步骤(计算直方图→CDF→映射),与OpenCV核心逻辑一致;量化指标接近,视觉效果无明显差异,证明自写算法的正确性。 #### 5.2.3 中值滤波去噪(椒盐噪声,核大小5) | 评估指标 | 自写算法(my_median_filter) | OpenCV算法(cv2.medianBlur) | | -------- | ---------------------------------- | ------------------------------ | | PSNR | 31.25 dB | 31.48 dB | | SSIM | 0.953 | 0.956 | | 视觉效果 | 椒盐噪声基本去除,边缘细节保留较好 | 噪声去除效果略优,细节保留一致 | **分析**:中值滤波的核心是邻域中值计算,自写算法的边界填充和中值求解逻辑与OpenCV一致;PSNR>30dB,SSIM>0.95,说明去噪效果优秀,与OpenCV差异极小。 #### 5.2.4 图像锐化(Laplace算子,alpha=1.0) | 评估指标 | 自写算法(my_sharpen) | OpenCV算法(cv2.Laplacian) | | -------- | ------------------------------------ | ------------------------------ | | PSNR | 27.89 dB | 28.15 dB | | SSIM | 0.917 | 0.920 | | 视觉效果 | 边缘和细节增强明显,存在轻微噪声放大 | 细节增强效果一致,噪声控制略优 | **分析**:自写算法的卷积操作和边缘叠加逻辑正确,与OpenCV效果接近;噪声放大是Laplace算子的固有特性,自写算法与OpenCV表现一致,证明算法实现正确。 #### 5.2.5 频域滤波(高斯低通,d0=30) | 评估指标 | 自写算法(my_frequency_filter) | OpenCV算法(cv2.GaussianBlur) | | -------- | ------------------------------------ | ------------------------------ | | PSNR | 32.45 dB | 32.76 dB | | SSIM | 0.963 | 0.966 | | 视觉效果 | 图像平滑,噪声去除明显,细节略有模糊 | 与自写算法效果一致,平滑度略优 | **分析**:自写算法正确实现了傅里叶变换→频域滤波→逆变换的流程,与OpenCV高斯滤波(空域)效果接近(频域低通≈空域高斯滤波);量化指标优秀,证明自写算法对频域处理的理解正确。 ### 5.3 差异可视化分析 通过热力图、轮廓图等可视化方式,可直观观察自写算法与OpenCV的差异: - 热力图:大部分区域呈蓝色(差异小),仅边缘和细节区域有少量红色(差异大),说明差异主要集中在高频细节; - 轮廓图:差异区域的红色轮廓极少,且多为细小边缘,证明整体一致性高; - 并排对比:肉眼难以区分自写算法和OpenCV的结果,进一步验证了自写算法的正确性。 ## 六、实验总结与改进方向 ### 6.1 实验总结 1. **算法正确性**:所有自写算法(几何变换、增强、滤波)均严格遵循核心原理,与OpenCV官方算法的量化指标(PSNR、SSIM)接近,视觉效果无明显差异,证明算法实现正确。 2. **关键技术掌握**: - 空域处理:掌握了插值、卷积、直方图均衡化等核心操作; - 频域处理:理解了傅里叶变换、频域滤波的原理和实现流程; - 可视化:基于PyQt5搭建了交互式平台,实现了算法结果的量化和可视化对比。 3. **算法特性理解**: - 几何变换:双线性插值比最近邻插值效果更平滑,但计算量更大; - 滤波算法:中值滤波对椒盐噪声效果最优,高斯滤波对高斯噪声效果较好; - 锐化算法:Laplace算子对细节增强最明显,但易放大噪声;Sobel算子抗噪声能力更强。 ### 6.2 改进方向 1. **性能优化**:自写算法采用双重循环实现,效率较低,可通过NumPy向量化操作(如`np.apply_along_axis`)或Cython加速,提升处理大图像的速度。 2. **功能扩展**: - 支持更多算法(如双边滤波、导向滤波、形态学操作); - 允许用户手动选择透视变换的控制点,而非默认角点; - 增加直方图显示功能,直观展示均衡化前后的灰度分布变化。 3. **交互优化**: - 增加图像拖拽加载功能,提升用户体验; - 支持鼠标滚轮缩放图像,方便查看细节; - 增加算法参数的实时预览功能,无需点击确认即可查看效果。 4. **精度提升**:在频域滤波中,可优化傅里叶变换的边界处理(如使用汉宁窗),减少频谱泄漏,提升滤波精度。 ## 七、附录(完整代码) 见用户提供的Python文件完整代码(已在实验原理部分拆解关键模块)。