Android设备上MNIST手写数字照片识别的图像预处理问题咨询
Android端MNIST手写数字识别:拍摄照片预处理与推理实现
嘿,刚好我之前在Android上做过类似的功能——用预训练MNIST模型识别照片里的单手写数字,给你梳理下具体的实现步骤,重点对应你提到的预处理环节:
一、核心预处理流程
这部分是保证模型识别准确率的关键,毕竟拍摄的照片和MNIST训练集的格式差异很大:
- 原始图像获取:用Android的CameraX或者系统相机API拿到拍摄后的
Bitmap对象就行。如果是竖屏拍摄,记得先做旋转校正(不过你说应用内图像尺寸一致,这步可以根据你的实际场景调整)。 - 转为黑白二值图像:MNIST训练集是白底黑字的单通道灰度图,所以必须把彩色照片转成符合要求的黑白格式。这里分两步:先转灰度,再做二值化(设定阈值区分背景和数字),代码示例(Kotlin):
// 第一步:将彩色图转为灰度图 val grayBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true) val canvas = Canvas(grayBitmap) val paint = Paint().apply { colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0f) }) } canvas.drawBitmap(originalBitmap, 0f, 0f, paint) // 第二步:二值化处理,区分背景和数字 val threshold = 127 // 可根据拍摄光线调整,比如光线暗的话降低阈值 val pixelArray = IntArray(grayBitmap.width * grayBitmap.height) grayBitmap.getPixels(pixelArray, 0, grayBitmap.width, 0, 0, grayBitmap.width, grayBitmap.height) for (i in pixelArray.indices) { // 提取灰度值 val grayValue = pixelArray[i] and 0xff // 低于阈值设为黑色(数字),高于设为白色(背景) pixelArray[i] = if (grayValue < threshold) 0xff000000.toInt() else 0xffffffff.toInt() } grayBitmap.setPixels(pixelArray, 0, grayBitmap.width, 0, 0, grayBitmap.width, grayBitmap.height)
二、适配模型输入尺寸
MNIST模型的标准输入是28x28的单通道灰度图,所以需要把处理后的黑白图调整到这个尺寸,同时要避免数字拉伸变形:
// 先裁剪出数字的核心区域(避免多余背景影响缩放) var leftBound = grayBitmap.width var rightBound = 0 var topBound = grayBitmap.height var bottomBound = 0 // 遍历像素找到数字的边界 for (y in 0 until grayBitmap.height) { for (x in 0 until grayBitmap.width) { if (grayBitmap.getPixel(x, y) == 0xff000000.toInt()) { // 黑色像素是数字 leftBound = min(leftBound, x) rightBound = max(rightBound, x) topBound = min(topBound, y) bottomBound = max(bottomBound, y) } } } // 给数字留一点边距,避免裁剪太紧凑 val margin = 10 val cropLeft = max(0, leftBound - margin) val cropTop = max(0, topBound - margin) val cropWidth = min(grayBitmap.width - cropLeft, rightBound - leftBound + 2 * margin) val cropHeight = min(grayBitmap.height - cropTop, bottomBound - topBound + 2 * margin) // 裁剪出数字区域 val croppedBitmap = Bitmap.createBitmap(grayBitmap, cropLeft, cropTop, cropWidth, cropHeight) // 缩放到模型要求的28x28尺寸 val modelInputBitmap = Bitmap.createScaledBitmap(croppedBitmap, 28, 28, true)
三、模型推理(以TensorFlow Lite为例)
把处理好的图像转成模型需要的张量格式,然后运行推理得到结果:
// 初始化预训练的TFLite模型(这里假设你已经把模型文件放到assets目录) val model = MnistModel.newInstance(context) // 创建输入张量:MNIST模型输入是[1,28,28,1]的float数组,值归一化到0-1 val inputBuffer = TensorBuffer.createFixedSize(intArrayOf(1, 28, 28, 1), DataType.FLOAT32) val inputPixels = FloatArray(28 * 28) for (i in 0 until 28 * 28) { val x = i % 28 val y = i / 28 val pixelValue = modelInputBitmap.getPixel(x, y) and 0xff // 注意:MNIST训练集是黑字白底,所以要把像素值反转(黑=255转为1,白=0转为0) inputPixels[i] = (255 - pixelValue) / 255.0f } inputBuffer.loadArray(inputPixels) // 运行推理 val outputs = model.process(inputBuffer) val resultArray = outputs.outputFeature0AsTensorBuffer.floatArray // 获取预测概率最高的数字 val predictedDigit = resultArray.indices.maxByOrNull { resultArray[it] } ?: -1
一些实用小提示
- 拍摄时尽量让数字居中,光线均匀,避免阴影或者反光;
- 二值化的阈值可以做成动态的,比如计算图像的平均灰度值作为阈值,适配不同光线环境;
- 如果你的模型是其他框架(比如ONNX Runtime),核心逻辑一样,只是张量转换的代码略有不同。
内容的提问来源于stack exchange,提问作者Lingviston




