MNIST数字OCR图像预处理:非正方形图像缩放画质损失解决方案咨询
嘿,我完全懂你现在的困扰——非正方形的数字图像缩放到MNIST要求的28x28时画质损失严重,这绝对是手写数字OCR预处理里的高频坑。我之前做类似项目时也踩过这个,给你几个亲测有效的解决方案:
解决方案1:保持比例的中心填充缩放
这个方法的核心是先按比例缩放图像,再把缩放后的图像居中放到28x28的空白画布上,避免直接拉伸导致的变形和画质损失。
具体步骤:
- 计算裁剪后图像的宽高,找到能适配28x28的最大缩放比例(确保宽或高不超过28)
- 用
INTER_AREA插值缩放图像(这个插值方法对缩小图像的画质保留最好) - 创建28x28的空白背景(和MNIST的背景色一致,比如黑色)
- 把缩放后的图像居中粘贴到空白画布上
代码示例:
import cv2 import numpy as np def resize_with_padding(img): # 假设输入是二值化后的单通道图像(0=背景,255=数字) h, w = img.shape[:2] # 计算最大缩放比例,保证缩放后宽/高不超过28 scale = min(28 / w, 28 / h) new_w, new_h = int(w * scale), int(h * scale) # 按比例缩放,INTER_AREA适合缩小图像 resized_img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA) # 创建28x28的空白背景(黑底) mnist_img = np.zeros((28, 28), dtype=np.uint8) # 计算居中偏移量 x_offset = (28 - new_w) // 2 y_offset = (28 - new_h) // 2 # 将缩放后的图像粘贴到中心 mnist_img[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized_img return mnist_img
解决方案2:基于轮廓的正方形裁剪
如果你的数字本身在图像里不是居中的,先通过轮廓提取把数字区域调整为正方形再缩放,能最大程度保留数字细节。
具体步骤:
- 提取数字的最大轮廓,获取它的最小外接矩形
- 通过透视变换把这个矩形转换成正方形(不管原始矩形是宽还是高,都补成正方形)
- 再把正方形图像缩放到28x28
代码示例:
def square_crop_then_resize(img): # 二值化(如果还没做的话) _, binary_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) # 提取数字轮廓 contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return np.zeros((28, 28), dtype=np.uint8) # 取面积最大的轮廓(假设是目标数字) cnt = max(contours, key=cv2.contourArea) # 获取数字的最小外接矩形 rect = cv2.minAreaRect(cnt) box = cv2.boxPoints(rect) box = np.int0(box) # 计算矩形的宽高,确定正方形的边长 width, height = int(rect[1][0]), int(rect[1][1]) square_size = max(width, height) # 定义透视变换的目标点(正方形的四个角) src_pts = box.astype("float32") dst_pts = np.array([ [0, square_size - 1], [0, 0], [square_size - 1, 0], [square_size - 1, square_size - 1] ], dtype="float32") # 计算透视变换矩阵并应用 M = cv2.getPerspectiveTransform(src_pts, dst_pts) square_img = cv2.warpPerspective(binary_img, M, (square_size, square_size)) # 缩放到28x28 resized_img = cv2.resize(square_img, (28, 28), interpolation=cv2.INTER_AREA) return resized_img
解决方案3:预处理增强优化缩放效果
在缩放前对图像做一些增强操作,能减少缩放后的细节损失:
- 自适应二值化:比普通二值化更适合光照不均的图像,能更好分离数字和背景:
adaptive_binary = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) - 去噪:用中值滤波或高斯滤波去掉图像中的噪点,避免缩放后噪点被放大:
denoised_img = cv2.medianBlur(adaptive_binary, 3) - 形态学增强:用膨胀操作突出数字轮廓,减少缩放后的边缘模糊:
kernel = np.ones((2,2), np.uint8) enhanced_img = cv2.dilate(denoised_img, kernel, iterations=1)
你可以把这些方法组合起来用,比如先做轮廓正方形裁剪,再做填充缩放,最后加预处理增强,效果会更好。另外,缩放时的插值方法要选对:缩小用INTER_AREA,如果是放大(比如原始数字很小)可以用INTER_CUBIC或INTER_LINEAR。
内容的提问来源于stack exchange,提问作者Casper




