去年冬天的一个周末,我突发奇想:能不能让电脑像人类一样,看着报纸上的数独图片直接解题?这个想法让我兴奋不已。数独作为经典的逻辑游戏,解题算法早已成熟,但让计算机"看懂"图片中的数字却是全新的挑战。这就是计算机视觉(CV)与光学字符识别(OCR)的用武之地。
整个项目可以分为三个关键阶段:
提示:完整代码已开源在GitHub,包含详细的安装说明和预训练模型。建议先通读本文理解原理,再动手实践。
拿到一张手机拍摄的数独图片,首先需要标准化处理。彩色图像包含的RGB三通道信息对于数字识别反而是干扰。使用OpenCV的cvtColor函数转换为灰度图:
python复制import cv2
gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) # BGR是OpenCV默认格式
接着用高斯模糊消除微小噪点。这里选择3×3的核大小,过大会模糊数字边缘,过小则降噪效果不佳:
python复制blurred = cv2.GaussianBlur(gray, (3, 3), sigmaX=0)
全局阈值处理在光照不均时会失效。自适应阈值(adaptiveThreshold)能根据局部区域亮度动态调整阈值,特别适合手机拍摄的图片:
python复制thresh = cv2.adaptiveThreshold(
blurred,
maxValue=255,
adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
thresholdType=cv2.THRESH_BINARY_INV, # 白底黑字转为黑底白字
blockSize=11, # 局部区域大小
C=2 # 从均值减去的常数
)
参数选择经验:
数独的粗网格线会干扰OCR识别。通过形态学开运算(先腐蚀后膨胀)提取水平/垂直线:
python复制# 水平核(宽度远大于高度)
horizontal_kernel = cv2.getStructuringElement(
cv2.MORPH_RECT,
ksize=(40, 1) # 调整40可改变检测的线长
)
# 垂直核(高度远大于宽度)
vertical_kernel = cv2.getStructuringElement(
cv2.MORPH_RECT,
ksize=(1, 40)
)
# 开运算提取线条
horizontal_lines = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel)
vertical_lines = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel)
合并线条并从原图中减去:
python复制grid_lines = cv2.add(horizontal_lines, vertical_lines)
clean_img = cv2.subtract(thresh, grid_lines)
假设图像已校正为正四边形,通过均匀分割获取81个单元格:
python复制height, width = clean_img.shape
cell_size = height // 9 # 假设是正方形图像
cells = []
for row in range(9):
for col in range(9):
x1 = col * cell_size
y1 = row * cell_size
x2 = x1 + cell_size
y2 = y1 + cell_size
cell = clean_img[y1:y2, x1:x2]
# 添加5像素白色边框,避免数字贴边
cell = cv2.copyMakeBorder(
cell,
top=5, bottom=5, left=5, right=5,
borderType=cv2.BORDER_CONSTANT,
value=0 # 黑色边框
)
# 统一缩放到28x28(MNIST标准尺寸)
cell = cv2.resize(cell, (28, 28))
cells.append(cell)
常见问题:若图片透视变形导致网格不规整,需先进行透视校正。可使用findContours找最大轮廓,再用getPerspectiveTransform矫正。
对比多种OCR方案后,Tesseract在打印体数字识别上表现最佳。安装时需注意:
bash复制# Ubuntu
sudo apt install tesseract-ocr
# MacOS
brew install tesseract
Python调用配置:
python复制import pytesseract
def recognize_digit(cell_img):
# 反转图像(Tesseract偏好黑底白字)
inverted = cv2.bitwise_not(cell_img)
# 关键配置:
# - psm 10: 单字符模式
# - whitelist: 只识别1-9
config = "--psm 10 --oem 3 -c tessedit_char_whitelist=123456789"
digit = pytesseract.image_to_string(
inverted,
config=config
).strip()
return int(digit) if digit else 0
对比度增强:对低质量图像,先使用CLAHE(对比度受限自适应直方图均衡化)
python复制clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
enhanced = clahe.apply(cell_img)
去噪处理:中值滤波对椒盐噪声特别有效
python复制denoised = cv2.medianBlur(cell_img, ksize=3)
形态学闭运算:填充数字笔画中的断裂
python复制kernel = np.ones((2,2), np.uint8)
closed = cv2.morphologyEx(cell_img, cv2.MORPH_CLOSE, kernel)
尽管深度学习也能解数独,但回溯算法在9×9标准数独上效率更高:
python复制def is_valid(board, row, col, num):
# 检查行
if num in board[row]:
return False
# 检查列
if num in [board[i][col] for i in range(9)]:
return False
# 检查3x3宫格
start_row, start_col = 3 * (row // 3), 3 * (col // 3)
for i in range(3):
for j in range(3):
if board[start_row + i][start_col + j] == num:
return False
return True
def solve(board):
for row in range(9):
for col in range(9):
if board[row][col] == 0:
for num in range(1, 10):
if is_valid(board, row, col, num):
board[row][col] = num
if solve(board):
return True
board[row][col] = 0 # 回溯
return False
return True
python复制import streamlit as st
from PIL import Image
st.title("数独视觉求解器")
uploaded_file = st.file_uploader("上传数独图片", type=["jpg", "png"])
if uploaded_file:
image = Image.open(uploaded_file)
st.image(image, caption="原始图像", use_column_width=True)
if st.button("求解"):
with st.spinner("处理中..."):
# 执行完整处理流程
grid = process_image(image)
solved = solve_sudoku(grid)
st.subheader("识别结果")
st.dataframe(grid)
st.subheader("解答")
st.dataframe(solved)
Dockerfile配置:
dockerfile复制FROM python:3.9-slim
RUN apt-get update && apt-get install -y tesseract-ocr
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
CMD ["streamlit", "run", "app.py"]
依赖管理:
code复制# requirements.txt
opencv-python==4.5.5
pytesseract==0.3.9
streamlit==1.11.0
numpy==1.22.4
这个项目最让我惊讶的是,看似简单的数独图片识别,竟涉及如此丰富的CV技术。从模糊的手机照片到精确的数字矩阵,每一步处理都影响着最终结果。这也让我深刻体会到,优秀的计算机视觉系统不仅需要强大的算法,更需要工程师对问题场景的深入理解和细致的参数调优。