如何让Tesseract识别连字符/减号以提取毕业证表格成绩数据
基于Tesseract识别毕业证表格成绩的优化方案
需求背景
使用Tesseract识别毕业证上的类表格结构成绩信息,采用将表格按列拆分后分别识别、再合并为tibble格式的方案,适配不同列的字符/整数类型要求。
现存问题
表格中空单元格使用连字符-填充,即使已经在字符白名单中添加了连字符、下划线和小数点,Tesseract仍无法识别该符号,导致不同列识别结果元素数量不一致、列长不匹配,无法合并为统一的tibble。
解决方案
1. 优化图像预处理强化连字符特征
连字符识别率低通常是因为笔画过细、对比度不足,调整预处理逻辑:
- 新增反色后膨胀操作,强化细笔画的连字符特征,处理完成后再恢复原始配色
- 取消透明化处理逻辑,避免连字符边缘被误判为背景抹除,改用二值化处理提升字符与背景的对比度
- 提高识别dpi到720,更高分辨率能保留更多连字符的细节特征
2. 修正Tesseract配置
原有白名单配置和词库规则会过滤连字符,调整引擎参数:
- 数字专用白名单将连字符放在最前面,避免被后续特殊规则覆盖
- 关闭Tesseract自带的词库匹配功能,避免连字符被误判为无效字符过滤
3. 新增列长对齐兜底逻辑
即使优化识别率后仍可能存在极个别识别误差,新增列长对齐逻辑:
- 以课程名列(长度最稳定)的行数为基准值
- 其余列如果行数少于基准值,自动补
-;如果行数多于基准值,过滤空识别结果后取前N条匹配基准长度
修正后代码
# 仅需下载一次德语训练集 tesseract_download("deu") # 加载德语识别引擎 deutsch <- tesseract("deu") library(pdftools) library(magick) library(tidyverse) # 渲染PDF第二页,提升dpi到720保留更多细节 bitmap <- pdf_render_page('./example.pdf', page = 2, dpi = 720) abiturzeugnis <- image_read(bitmap) # 优化预处理逻辑,强化连字符特征 no_grid <- abiturzeugnis %>% image_deskew() %>% image_quantize(colorspace = "gray") %>% # 二值化提升对比度 image_threshold(type = "black", threshold = "50%") %>% image_threshold(type = "white", threshold = "50%") %>% # 反色后膨胀强化细笔画,再反色恢复 image_negate() %>% image_morphology(method = "dilate", kernel = "rectangle:2x1") %>% image_negate() # 优化OCR识别函数 img_ocr_fun <- function(trim_width, trim_start, char_num = TRUE) { # 调整白名单顺序,关闭词库匹配避免连字符被过滤 num_only <- tesseract::tesseract( options = list( tessedit_char_whitelist = "-._0123456789 ", load_system_dawg = FALSE, load_freq_dawg = FALSE ) ) combo <- tesseract::tesseract( options = list( tessedit_char_whitelist = paste0( c("-", letters, LETTERS, " ", "._0123456789()", "ä", "ö", "ü", "Ä", "Ö", "Ü"), collapse = ""), load_system_dawg = FALSE, load_freq_dawg = FALSE ) ) input_char <- if (isTRUE(char_num)) { num_only } else { combo } # 调整裁剪参数匹配新的dpi ocr_res <- no_grid %>% image_crop(geometry_area(trim_width, 3816, trim_start, 1680)) %>% ocr(engine = input_char) %>% str_split(pattern = "\n") %>% unlist() %>% str_squish() %>% enframe(name = NULL, value = "value") %>% filter(!is.na(value)) return(ocr_res) } # 按列识别 all_ocr <- list( trim_width = c(750, 115, 105, 100, 100, 600), trim_start = c(270, 1210, 1460, 1700, 1950, 2125), char_num = c(FALSE, TRUE, TRUE, TRUE, TRUE, FALSE) ) %>% purrr::pmap(img_ocr_fun) # 列长对齐逻辑,以课程名列长度为基准 base_len <- nrow(all_ocr[[1]]) all_ocr_aligned <- map(all_ocr, function(col_df) { col_vec <- col_df$value if (length(col_vec) < base_len) { # 长度不足补- col_vec <- c(col_vec, rep("-", base_len - length(col_vec))) } else if (length(col_vec) > base_len) { # 长度过长过滤空值后匹配基准长度 col_vec <- col_vec[col_vec != ""][1:base_len] col_vec[is.na(col_vec)] <- "-" } return(tibble(value = col_vec)) }) # 合并为结果表 data_df <- all_ocr_aligned %>% bind_cols() %>% set_names(nm = c("Fach", "11 I", "11 II", "12 I", "12 II", "Note")) data_df
内容的提问来源于stack exchange,提问作者RKF
相关产品推荐
相关产品推荐

