2018年2月4日 星期日

技術筆記 OCR


印表玩具

OptRec專案之分析及測試
因為想多了解STM32在影像分析可達到什麼程度,開始在網路上蒐羅確實可行的範例。OptRec就是其中之一。這是位捷克研究生的碩士論文專案,篇名是Optical Recognition and Analysis for Home Metering。專案與OCR有關。區分成兩個階段。第一階段,是STM32和Camera互動範例技術報告。第二階段是完整流程。YouTube範例作者論文

功能很單純,就是透過鏡頭讀取畫面,送入OCR模組分析文字,再將資料顯示螢幕上,並且記錄入SD卡。互動方式使用觸碰螢幕。

GOCR原理


ocr_gocr : 是入口ocr_algorithm
    job_t job1, *job;
    job=OCR_JOB=&job1;
    // Picture buffer allocation
    img_buffer = malloc(crop_width*crop_height);
    img = malloc(crop_width*crop_height);
    grayscale
    // Assign picture to job
    job->src.p.p = img;
    job->src.p.bpp = 1;
    // Main OCR loop
    img2asc(job);   主要準備開始啟動
    印出結果
    // Free memory
    free_textlines(&(job->res.linelist));
    job_free_image(job);
    free(img);

運作的方式是JOB,可以簡單區分成下面步驟

  scan_boxes( job, pp );
從上往下,分別在X,Y軸方向投影,得到box list


  remove_dust( job ); /* from the &(job->res.boxlist)! */
  detect_barcode( job );  /* mark barcode */
  detect_pictures( job ); /* mark pictures */

所有box的平均寬度為avgwidth,平均高度為avgheight,符合box.width > 4 * avgwidth || height > 4*avgheight條件並且相近大小的box少於4個的box認為是圖像box。

  remove_pictures( job ); /* do this as early as possible, before layout */
  glue_holes_inside_chars( pp ); /* including count subboxes (holes)  */
  detect_rotation_angle( job );
  detect_text_lines( pp, job->cfg.mode ); /* detect and mark job->tmp.ppo */

Line detection is very important for good recognition. For example it is difficult to distinguish between lowercase letter p and uppercase letter P without having a baseline (same total height). The lowercase version of p has a depht (the lower end is below the baseline) and therefore its easy to distinguish from the uppercase version if the baseline is known. The line detection is responsible for finding the baseline of every text line.
Lines of characters are detected by looking for interline spaces. These are characterized by a large number of non-black pixels in a row. Image rotation (skewing) presents a problem, therefore the program first looks only at the left half of the image. When a line is found, the left half of the right side is scanned, because lines are often short. The variation in height gives an indication of the rotation angle. Using this angle, a second run detects lines more accurately. Line detection may fail if there is dust on the image.

  add_line_info( job /* , &(job->res.boxlist) */);
  divide_vert_glued_boxes( pp, job->cfg.mode); /* after add_line_info, before list_sort! */
  remove_melted_serifs( job, pp ); /* make some corrections on pixmap */
  glue_broken_chars( job, pp ); /* 2nd glue */
  remove_rest_of_dust( job );
  list_sort(&(job->res.boxlist), sort_box_func);
  measure_pitch( job );
  char_recognition( pp, job->cfg.mode);

gocr的識別不是機器學習式的學習,沒有training過程,完全靠先驗的規則,因此只能識別英文字符,數字,標點等。識別主要是一個filter鏈路,每個filter決定box是否是該字符,是則略過後續filter。

a,從box外引出一條射線從某個方向(左,右,上,下)某個坐標(x,y)向box內部,第一個交點位置必須符合某個字符的先驗規則;


 b,經過box的一條直線與字符的交點個數必須符合某個字符的先驗規則,算法:計算這樣的點(如從左向右:Pixel(x,y) = white && Pixel(x+1,y) = black ) 的個數


 c,孔洞的個數必須符合某個字符的先驗規則,比如A有一個洞;這一步只是判斷,實際工作在第5步已經完成。


  compare_unknown_with_known_chars( pp, job->cfg.mode);
  try_to_divide_boxes( pp, job->cfg.mode);
參考來源

各檔案列表
output : debug使用
unicode : 只是顯示文字

GOCR移植OptRec


硬體內容
  stm32f429 discovery (相同)
  ov7670 (相同)
  sdcard (具備)
軟體內容
  console tool chain (非keil)
  sdcard : fatfs
  ocr : GOCR v0.50

這功能在尋常桌機加OpenCV看來,實在是一塊小蛋糕。但在嵌入式系統,包含這個多程式碼,且有UI互動界面,實屬難得啊。分析模組如下。

main.c
   宣告許多起始動作
   控制流程
while(1){
     很特別的command dispatch架構
    /* TASK 1 - Home page display/refresh */
    if (Time_elapsed(200, &task_arr[1])){
        if (sys_mode == SYS_INIT) {
                // Display main page
                LCD_ILI9341_Button_DisableAll();
                Display_Home_Page();
                sys_mode = SYS_HOME;
            }
    }
      /* TASK 2 - Buttons/touch service */
     if (Time_elapsed(100, &task_arr[2])){
            // Home page buttons
            // - Manual mode
            if (button_pressed == BUTTON_MANUAL_MODE){
                LCD_ILI9341_Button_DisableAll();
                Camera_Enable();
                sys_mode = SYS_MANUAL_MODE;
            }
            // - Automatic mode
            else if (button_pressed ==  BUTTON_AUTOMATIC_MODE){
                LCD_ILI9341_Button_DisableAll();
                Display_Automatic_Mode_Page();
                sys_mode = SYS_AUTOMATIC_MODE;
            }
            // - Display data logs
            else if (button_pressed == BUTTON_DISPLAY_LOGS){
                LCD_ILI9341_Button_DisableAll();
                Display_Data_Logs_Page();
                sys_mode = SYS_END;
            }
            // - Settings
            else if (button_pressed == BUTTON_SETTNGS_MODE){
                LCD_ILI9341_Button_DisableAll();
                Display_Settings_Page();
                sys_mode = SYS_SETTINGS_MODE;
            }
            ...還有很多... 這種寫法很特別
     }
     /* TASK 3 - Clock display/refresh */
     if (Time_elapsed(1000, &task_arr[3])){
            if (sys_mode == SYS_HOME) {
                Display_Clock();
            }
     }
     /* TASK 4 - Battery voltage display/refresh */
        if (Time_elapsed(2000, &task_arr[4])){
            if (sys_mode == SYS_HOME) {
                Display_Battery();
            }
     }
}
包括一些callback的handler

/* DCMI DMA interrupt */
void DMA2_Stream1_IRQHandler(void){

/* LCD SPI interrupt */
void DMA2_Stream4_IRQHandler(void){

/* Button GPIO interrupt */
void EXTI0_IRQHandler(void) {
 
/* TOUCH GPIO interrupt */
void EXTI15_10_IRQHandler(void) {

其他程式

1)err_handler : 顯示一些文字,並且一直繞while

2)fatfs_control : APP用到的指令
fats_spi : 一開始設定
fat有個目錄有七個相關檔案

3)lcd_fonts : 字型resource
lcd_ili9341 : 畫圖畫線等
lcd_ili9341_button : 畫出按鈕,並且按下去會有相對記錄
lcd_spi : 設定和DMA等等
background : 印出圖片背景固定顏色

4)ov7670_control : camera固定設定

5)rtc_awu_control : RTC_AWU_SetDateTime RTC_AWU_GetUnixTimeStamp
     RTC_AWU_GetDateTime RTC_AWU_SetAlarm

6)system_control : init一堆東西 顯示各個不同畫面
stm32f4xx_it 固定寫法
system_stm32f4xx 固定寫法

7)touch_i2c : touch
touch_stmpe811

記憶體使用


是影像處理很重要的部分。
fmc_sdram : 只有init,真正使用應該是malloc 不過下載項目中沒有sct之類檔案
syscalls
* Modification of system memory management (e.g. malloc etc.).
* Simply moves heap region to external SDRAM.
* Important is to initialize FMC for SDRAM soon as possible.
* Optimized for 32F429IDISCOVERY board.
會換掉 _sbrk
caddr_t _sbrk(int incr) {
    static char *heap_end;
    static char *sdram_start = (char*) SDRAM_MEM_START;
    static char *sdram_end = (char*) SDRAM_LCD_START1;
    重點應該是 static char * 雖然是function內部變數,但
    char *prev_heap_end;
    // Modify system memory management
    if (heap_end == 0) {
        heap_end = sdram_start;
    }
    prev_heap_end = heap_end;
    if (heap_end + incr > sdram_end) {
        return  (caddr_t) -1;
    }
    heap_end += incr;
    return (caddr_t) prev_heap_end;
}

沒有留言:

張貼留言