构建手写数字识别Web应用:前后端完整解决方案
在深度学习项目中,将模型部署为Web应用是一个重要的环节。本文将详细介绍如何构建一个完整的手写数字识别Web应用,涵盖前端界面设计、后端API开发和模型部署。
技术栈概览
· 前端: HTML5 Canvas + JavaScript + Bootstrap
· 后端: Python Flask + TensorFlow/Keras
· 模型: 卷积神经网络(CNN)
· 部署: 本地服务器或云平台
1. 后端API开发 (Flask)
首先,我们创建一个Flask后端服务来处理图像和进行预测。
app.py - 主后端应用
```python
from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
import numpy as np
import cv2
import base64
import io
from PIL import Image
import tensorflow as tf
import os
app = Flask(__name__)
CORS(app) # 允许跨域请求
# 全局变量存储模型
model = None
def load_model():
"""加载预训练模型"""
global model
try:
model = tf.keras.models.load_model('models/handwritten_digit_model.h5')
print("模型加载成功")
except Exception as e:
print(f"模型加载失败: {e}")
model = None
def preprocess_image(image):
"""
预处理图像以匹配模型输入要求
"""
# 转换为灰度图
if len(image.shape) == 3:
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
# 调整大小为28x28
image = cv2.resize(image, (28, 28))
# 反转颜色(MNIST是白字黑底)
image = cv2.bitwise_not(image)
# 归一化
image = image.astype('float32') / 255.0
# 调整形状
image = image.reshape(1, 28, 28, 1)
return image
@app.route('/')
def index():
"""渲染主页面"""
return render_template('index.html')
@app.route('/health', methods=['GET'])
def health_check():
"""健康检查端点"""
return jsonify({
'status': 'healthy',
'model_loaded': model is not None
})
@app.route('/predict', methods=['POST'])
def predict_digit():
"""
预测手写数字的API端点
接收Base64编码的图像数据
"""
if model is None:
return jsonify({
'error': '模型未加载',
'success': False
}), 500
try:
# 获取JSON数据
data = request.get_json()
if not data or 'image' not in data:
return jsonify({
'error': '未提供图像数据',
'success': False
}), 400
# 解码Base64图像
image_data = data['image'].split(',')[1] # 移除data:image/png;base64,前缀
image_bytes = base64.b64decode(image_data)
# 转换为PIL图像
image = Image.open(io.BytesIO(image_bytes))
# 转换为numpy数组
image_array = np.array(image)
# 预处理图像
processed_image = preprocess_image(image_array)
# 进行预测
predictions = model.predict(processed_image, verbose=0)
# 获取预测结果
predicted_digit = int(np.argmax(predictions[0]))
confidence = float(np.max(predictions[0]))
# 获取所有数字的概率
probabilities = {
str(i): float(predictions[0][i])
for i in range(10)
}
return jsonify({
'success': True,
'prediction': predicted_digit,
'confidence': confidence,
'probabilities': probabilities,
'message': f'识别结果: {predicted_digit} (置信度: {confidence:.2%})'
})
except Exception as e:
return jsonify({
'error': f'预测过程中发生错误: {str(e)}',
'success': False
}), 500
@app.route('/batch_predict', methods=['POST'])
def batch_predict():
"""
批量预测多个数字
"""
if model is None:
return jsonify({
'error': '模型未加载',
'success': False
}), 500
try:
data = request.get_json()
images_data = data.get('images', [])
results = []
for img_data in images_data:
# 解码Base64图像
image_data = img_data.split(',')[1]
image_bytes = base64.b64decode(image_data)
image = Image.open(io.BytesIO(image_bytes))
image_array = np.array(image)
# 预处理和预测
processed_image = preprocess_image(image_array)
predictions = model.predict(processed_image, verbose=0)
predicted_digit = int(np.argmax(predictions[0]))
confidence = float(np.max(predictions[0]))
results.append({
'prediction': predicted_digit,
'confidence': confidence
})
return jsonify({
'success': True,
'results': results
})
except Exception as e:
return jsonify({
'error': f'批量预测失败: {str(e)}',
'success': False
}), 500
@app.route('/model_info', methods=['GET'])
def get_model_info():
"""获取模型信息"""
if model is None:
return jsonify({'error': '模型未加载'}), 404
try:
# 获取模型摘要信息
model_summary = []
model.summary(print_fn=lambda x: model_summary.append(x))
return jsonify({
'success': True,
'model_layers': len(model.layers),
'input_shape': model.input_shape,
'output_shape': model.output_shape,
'summary': '\n'.join(model_summary)
})
except Exception as e:
return jsonify({
'error': f'获取模型信息失败: {str(e)}',
'success': False
}), 500
if __name__ == '__main__':
# 加载模型
load_model()
# 启动Flask应用
app.run(
host='0.0.0.0',
port=5000,
debug=True,
threaded=True
)
```
requirements.txt - 后端依赖
```txt
flask==2.3.3
flask-cors==4.0.0
tensorflow==2.13.0
numpy==1.24.3
opencv-python==4.8.1.78
Pillow==10.0.1
gunicorn==21.2.0
```
2. 前端界面开发
接下来,我们创建一个响应式的前端界面,使用HTML5 Canvas进行手写输入。
templates/index.html - 主页面
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写数字识别系统</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- 自定义样式 -->
<style>
.drawing-canvas {
border: 2px solid #007bff;
border-radius: 8px;
cursor: crosshair;
background-color: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.probability-bar {
height: 20px;
background: linear-gradient(90deg, #4CAF50, #8BC34A);
border-radius: 10px;
transition: width 0.3s ease;
margin-bottom: 5px;
}
.result-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px;
}
.drawing-area {
background: #f8f9fa;
border-radius: 15px;
padding: 20px;
}
.loading-spinner {
display: none;
width: 3rem;
height: 3rem;
}
.btn-custom {
background: linear-gradient(45deg, #667eea, #764ba2);
border: none;
color: white;
transition: all 0.3s ease;
}
.btn-custom:hover {
