マイクロコントローラーでの光学式文字認識





現在、光学式文字認識は、テキスト認識とデジタル化、文書認識、ナンバープレート認識、銀行カード番号の決定、検針の読み取り、地図作成用の家番号の決定(Googleストリートビュー)などのアプリケーションのソリューションの一部です。 d。



文字認識とは、クラスの記号と比較するための記号の特定のセットを取得するために、その画像を分析することを意味します[ 1 ]。 そのようなセットの選択とその決定のための方法は、異なる認識方法によって区別されますが、ほとんどの場合、画像のすべてのピクセルに関する同時情報が必要です。



後者の状況と十分な量の計算により、光学式文字認識に低電力コンピューティングデバイス(マイクロコントローラー)を使用することができなくなります。 「はい、なぜですか?」、情報に通じた読者は、「コンピューティングデバイスのパワーは常に増加していますが、価格は低下しています!」と叫ぶでしょう[ 2、3 ]。 答えはこれだとしましょう:ただ疑問に思う、マイクロコントローラーを使用できる程度に認識方法を単純化することは可能ですか?



それは可能であることが判明し、さらに、SFの分野に属していると思われるものが可能であることが判明しました:





そして、これはすべてマイクロコントローラー上で。



メソッドの主なアイデア



次に、メソッド自体について詳しく説明します。 たとえば、Aのさまざまなスタイルを考えてみましょう。









目に見える違いにもかかわらず、構造型の一般的な兆候を区別できます。これは、大文字Aの必要な兆候です(連続した文字の場合 )。つまり、問題のシンボルが大文字Aである場合、閉じた領域と開いた領域が含まれます。









これらの記号は必要ですが、十分ではないことを再度強調します。指定されたタイプの2つの領域の周囲の輪郭を記述する場合、









大文字のAである必要はありません。たとえば、D、Z、R、小文字の手書きAなどが可能です。









ただし、シンボルの要素として領域を使用すると、十分な記号を生成できます。また、膨大な数の英数字に対して、十分な記号のみを生成できます! クラスごとに形成するのは非常に簡単で、 ABBYYTeamが手書き認識[ 1 ]で使用する構造的特徴とは異なり、その変動性は非常に低く、自動的に形成することが可能です。 つまり、このような文字は、印刷文字と手書き文字の両方でうまく機能します。



認識装置



この方法の最初のテストは[ 4 ]で説明されています。 この方法は、7セグメントインジケータのマウスからプリミティブカメラで取得した、または紙に印刷した1桁でテストされました。 最初の成功の後、一連の文字を認識する可能性をテストする自然な欲求が生じました。そのためには、別のカメラを使用する必要があります。 OV7670カメラ(0.3MP)を使用しました。 回路の残りの主要コンポーネントは変更されていません-これらはArduinoとESP8266ですが、機能は変更されています。 Arduinoを使用して、カメラをマスターオシレーターとして初期化し、認識された文字を受け取り、インジケーターに表示します。 ESP8266はカメラからの画像の受信とその認識に従事しており、さらに、Arduinoへのデータ転送を提供し、認識された情報をWiFiを介して外部デバイス(スマートフォンなど)に送信します。 デバイスの使用電気回路を図に示します:









画像は下部のスリットからデバイスに入り、ミラーから反射してカメラに入ります。 画像は同じミラーを介してLEDで照らされます。 デバイスの機械回路を図に示します。









作業プロトタイプの写真

作業用プロトタイプの最初のバージョン:











作業用プロトタイプの2番目のバージョン:













ESP8266で画像を取得する



初期化中のカメラ設定は[ 5 ]から取得されます。 フレームレートは約0.4 fpsで、ESP8266のピン数が十分でないため、画像の各輝度バイトの上位6ビットのみが処理されます(カメラはYUVモードで構成されます)。 イメージを取得するには、有限状態マシン(状態マシン)が使用されます。









OV7670カメラのデータシートによる[ 6 ]















次のカメラの状態、動作中の状態および信号を区別できます。

ステータス名、 番号 ステータスの説明 遷移信号

別の状態へ
カムオフ、0 カメラ

仕事の準備ができていません
vzz

(vsync = 1、href = 0 、pclk = 0
フラポーズ1 一時停止

フレーム間。 フレームの開始を待っています。
zzz(vsync = 0、href = 0、pclk = 0)
framebeg、2 読書

フレーム。 フレーム内の行の開始を待っています。
zhz(vsync = 0、href = 1、pclk = 0)
framebeg、2 読書

フレーム。 読み取り後、フレームの終わりを待つ

最後のピクセル
vzz

(vsync = 1、href = 0 、pclk = 0
fbyteread、 3 明るさ

バイト読み取り。 一時停止を待っています

色差バイトの前。
zhz(vsync = 0、href = 1、pclk = 0)
ポーズ 4 一時停止

色差バイトの前。 待っている

色差バイトの読み取りを開始します。
zhp

(vsync = 0、href = 1 、pclk = 1
sbyteread、 5 色差

バイト読み取り。 一時停止を待っています

輝度バイトの前。
zhz(vsync = 0、href = 1、pclk = 0)
休眠 6 一時停止

輝度バイトの前。 待っている

行末。
zzz

(vsync = 0、href = 0、 pclk = 0)
休眠 6 一時停止

輝度バイトの前。 スタートを待っている

輝度バイトを読み取ります。
zhp

(vsync = 0、href = 1 、pclk = 1


マシンの実装は、[ 7 ]で説明されているのと同じ原理に基づいています。 マシン全体は、その遺伝子(最初のコンポーネントにキーが含まれる3次元ベクトル)、および2番目(新しい状態の名前、3番目に関数の名前)によって記述されます。 キーには、現在の状態と遷移信号からの情報が含まれています。 キーと信号を生成するには、ビット演算が使用されます。 実装の詳細は、カメラリーダーモジュールのコードから明らかです。



user_main.c
#include "ets_sys.h" #include "osapi.h" #include "os_type.h" #include <gpio.h> #include "driver/uart_register.h" #include "user_config.h" #include "user_interface.h" #include "driver/uart.h" #include "readCam.h" #define DELAY 5000 /* milliseconds */ LOCAL os_timer_t cam_timer; uint16_t frN; extern uint8_t pixVal; uint8_t rN[10]; LOCAL void ICACHE_FLASH_ATTR getNFrame(void *arg){ uint16_t sig, sV,sH,sP; uint16_t pVal; uint16_t d7,d6,d5,d4,d3,d2; stateMashine camSM; ets_uart_printf("getNFrame...\r\n"); initSMcm(&camSM); while(frN<20){ system_soft_wdt_feed(); pVal= *GPIO_IN; sV=((pVal&(1UL<<VSYNC))>>VSYNC); sH=((pVal&(1UL<<HREF))>>HREF); sP=((pVal&(1UL<<PCLK))>>PCLK); sig=4*sV+2*sH+sP*sH; d7=((pVal&(1UL<<D7))>>D7); d6=((pVal&(1UL<<D6))>>D6); d5=((pVal&(1UL<<D5))>>D5); d4=((pVal&(1UL<<D4))>>D4); d3=((pVal&(1UL<<D3))>>D3); d2=((pVal&(1UL<<D2))>>D2); pixVal=128*d7+64*d6+32*d5+16*d4+8*d3+4*d2; exCAM(&camSM,sig,&frN,rN); } } uint32 ICACHE_FLASH_ATTR user_rf_cal_sector_set(void) { enum flash_size_map size_map = system_get_flash_size_map(); uint32 rf_cal_sec = 0; switch (size_map) { case FLASH_SIZE_4M_MAP_256_256: rf_cal_sec = 128 - 8; break; case FLASH_SIZE_8M_MAP_512_512: rf_cal_sec = 256 - 5; break; case FLASH_SIZE_16M_MAP_512_512: case FLASH_SIZE_16M_MAP_1024_1024: rf_cal_sec = 512 - 5; break; case FLASH_SIZE_32M_MAP_512_512: case FLASH_SIZE_32M_MAP_1024_1024: rf_cal_sec = 1024 - 5; break; default: rf_cal_sec = 0; break; } return rf_cal_sec; } void ICACHE_FLASH_ATTR user_init(void){ void (*cbGetFrame)(void *arg); cbGetFrame=(void*)getNFrame; UARTInit(BIT_RATE_921600); user_gpio_init(); os_timer_disarm(&cam_timer); os_timer_setfn(&cam_timer, (os_timer_func_t *)cbGetFrame, NULL); os_timer_arm(&cam_timer, DELAY, 0); }
      
      







readCam.h
 #ifndef INCLUDE_READCAM_H_ #define INCLUDE_READCAM_H_ #define GPIO_IN ((volatile uint32_t*) 0x60000318) #define WP 320 #define HP 240 #define PIXTYP 0 //image __________________________________________ #define IMAGEY0 60 #define IMAGEH HP/3 //____________________pins_____________________ #define VSYNC 15 #define HREF 13 #define PCLK 3 #define D7 4 #define D6 12 #define D5 0 #define D4 14 #define D3 2 #define D2 5 //*************signals OV7670***************** #define ZZZ 0 #define VZZ 4 #define ZHZ 2 #define ZHP 3 //*************states OV7670******************* #define CAMOFF 0 #define FRAPAUSE 1 #define FRAMEBEG 2 #define FBYTEREAD 3 #define FPAUSE 4 #define SBYTEREAD 5 #define SPAUSE 6 #define SSCC 40//max state_signal_condition count #define STATE_L 5 #define STATE_V 0x1F #define SIG_L 8 #define SIG_V 0xFF typedef struct { uint8 pix[WP] ; }linePixel; typedef struct gen{ uint8_t state; uint8_t sig; uint8_t stateX; void *fp; }gen; typedef struct stateMashine{ uint8_t count; uint16_t ssc[SSCC]; uint8_t stateX[SSCC]; void *fPoint[SSCC]; void *fpd; }stateMashine; #endif /* INCLUDE_READCAM_H_ */
      
      







readCam.c
 #include "ets_sys.h" #include "osapi.h" #include "os_type.h" #include <gpio.h> #include "driver/uart_register.h" #include "user_config.h" #include "user_interface.h" #include "driver/uart.h" #include "readCam.h" void sendLine(uint16_t lN); void ICACHE_FLASH_ATTR sendFramMark(void); void ICACHE_FLASH_ATTR sendCtr3Byte(uint8_t typ,uint16_t len); void user_gpio_init(void); void sendByte(uint8_t bt); void ICACHE_FLASH_ATTR initSMcm(stateMashine *psm); void exCAM( stateMashine *psm,uint8_t sig,uint16_t *frameN,uint8_t *rN); int indexOf(stateMashine *psm,uint16_t ssc); linePixel lp; uint8_t pixVal; void exCAM( stateMashine *psm,uint8_t sig,uint16_t *frameN,uint8_t *rN){ int16_t ind; uint16_t lN; uint16_t pN; static uint8_t state=CAMOFF,stateX=CAMOFF; static void (*pfun)()=NULL; uint16_t stateSigCond=0; stateSigCond|=((state&STATE_V)<<(16-STATE_L))|((sig&SIG_V)<<(16-STATE_L-SIG_L)); ind=indexOf(psm,stateSigCond); if(ind>-1) stateX=(*psm).stateX[ind]; if(ind>-1) pfun=(*psm).fPoint[ind]; else pfun=(*psm).fpd; pfun(frameN,&lN,&pN,rN); state=stateX; } void _cm0(){} void _cm1(uint16_t *fN,uint16_t *lN,uint16_t *pN){//new frame sendFramMark(); sendCtr3Byte(PIXTYP,0); (*lN)=0; } void _cm2(uint16_t *fN,uint16_t *lN,uint16_t *pN){//frame end if(*lN==HP-1)(*fN)++; } void _cm3(uint16_t *fN,uint16_t *lN,uint16_t *pN){//new line uint16_t pixN; (*pN)=0; // pixN=(*pN);//right image pixN=WP-1-(*pN);//revers image (lp).pix[pixN]=pixVal; (*pN)++; } void _cm4(uint16_t *fN,uint16_t *lN,uint16_t *pN){// first byte uint16_t pixN; // pixN=(*pN);//right image pixN=WP-1-(*pN);//reverse image (lp).pix[pixN]=pixVal; // if(pixN<WP-1)(*pN)++;//right image if(pixN)(*pN)++;//reverse image } void _cm5(uint16_t *fN,uint16_t *lN,uint16_t *pN,uint8_t *rN){//end line uint16_t lineN; lineN=(*lN); sendLine(lineN); if((*lN)<HP-1)(*lN)++; } void _cm99(){} int indexOf(stateMashine *psm,uint16_t ssc){ uint8_t i,count; count=(*psm).count; for(i=0;i<count;i++){ if((*psm).ssc[i]==ssc) return i; } return -1; } void ICACHE_FLASH_ATTR initSMcm(stateMashine *psm){ uint8_t i,count; count=10; gen gen[]={ {CAMOFF,VZZ,FRAPAUSE,_cm0},//0#1 {FRAPAUSE,ZZZ,FRAMEBEG,_cm1},//1#2 {FRAMEBEG,VZZ,FRAPAUSE,_cm2},//2#1 {FRAMEBEG,ZHZ,FBYTEREAD,_cm3},//2#3 {FBYTEREAD,ZHP,FPAUSE,_cm0},//3#4 {FPAUSE,ZHZ,SBYTEREAD,_cm0},//4#5 {SBYTEREAD,ZHP,SPAUSE,_cm0},//5#6 {SPAUSE,ZHZ,FBYTEREAD,_cm4},//6#3 {SPAUSE,ZZZ,FRAMEBEG,_cm5},//6#2 {FPAUSE,ZZZ,FRAMEBEG,_cm5},//5#2 }; (*psm).count=count; for(i=0;i<count;i++){ (*psm).ssc[i]=0; (*psm).ssc[i]|=((gen[i].state&STATE_V)<<(16-STATE_L))| ((gen[i].sig&SIG_V)<<(16-STATE_L-SIG_L)); (*psm).stateX[i]=gen[i].stateX; (*psm).fPoint[i]=gen[i].fp; } (*psm).fpd=_cm99; } void sendByte(uint8_t bt){ uint16_t lenBuff; uint8_t buf[TX_BUFF_SIZE]; while(lenBuff){ lenBuff = (READ_PERI_REG(UART_STATUS(0))>>UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT; } buf[lenBuff] =bt; uart0_tx_buffer(buf, lenBuff + 1); } void sendLine(uint16_t lN){ uint16_t j; uint8_t sByt; for(j=0;j<WP;j++){ sByt=(lp).pix[j]; if(lN<IMAGEY0||lN>(IMAGEY0+IMAGEH))sByt=0xFF; sendByte(sByt); } } void ICACHE_FLASH_ATTR user_gpio_init(void) { ets_uart_printf("GPIO initialisation...\r\n"); PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0); gpio_output_set(0, 0, 0, BIT0); // Set GPIO0 as input PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2); gpio_output_set(0, 0, 0, BIT2); // Set GPIO2 as input PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3); gpio_output_set(0, 0, 0, BIT3); // Set GPIO3 as input PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4); gpio_output_set(0, 0, 0, BIT4); // Set GPIO4 as input PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5); gpio_output_set(0, 0, 0, BIT5); // Set GPIO5 as input PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12); gpio_output_set(0, 0, 0, BIT1); // Set GPIO13 as input PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14); gpio_output_set(0, 0, 0, BIT14); // Set GPIO14 as input PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13); // Set GPIO13 function gpio_output_set(0, 0, 0, BIT13); // Set GPIO13 as input PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15); gpio_output_set(0, 0, 0, BIT15); // Set GPIO15 as input ets_uart_printf("...init done!\r\n"); } void ICACHE_FLASH_ATTR sendFramMark(void){ sendByte(42); sendByte(42); } void ICACHE_FLASH_ATTR sendCtr3Byte(uint8_t typ,uint16_t len){ uint8_t lLen,hLen; sendByte(typ); lLen=len&0xFF; hLen=(len&(0xFF<<8))>>8; sendByte(lLen); sendByte(hLen); }
      
      







画像処理



画像処理は、行ごとの二値化、取得したセグメントの結合、取得した図の分析と合成で構成されます。 処理の目的は、図に含まれる領域の特性を含む、統合的な特徴の形成です。 主なアイデアは単純ですが、その実装には、この記事のフレームワークでは開示できない特定のポイントがいくつか含まれています。



認識プロセスの視覚化



認識プロセスをデバッグするために、ソースイメージ、2値化イメージ、およびマイクロコントローラーが「見る」または「理解する」イメージのPCでの視覚化を使用しました。 後者は目を大きく喜ばせないという事実にもかかわらず、その詳細はシンボルを認識するのに十分です。 図は、視覚化の例を示しています。











カメラの同期エラーが原因で、元の画像に輝度バイトではなく色差のある線が表示される場合があります。 この場合、画像はぼやけて認識されません。 現在の段階でそのような状況を処理するタスクは提起されませんでした







また、テキストが正しく配置されていない場合、認識は行われません。







視覚化のために、nodeWebkitを使用してJavaスクリプトで記述された小さなプログラムを使用しました。



app.js
* COMポートを使用するには、nodewebkitの下にnodeJS "serialport"モジュールをアセンブルする必要があります

 var btn = document.getElementById('com'); var gui = require("nw.gui"); var select_com = document.getElementById('select_com'); var bdr = document.getElementById('bdr'); var canvas = document.getElementById('canvas'); var dev = document.getElementById('dev'); var ctx = canvas.getContext('2d'); var width = 320, height = 240; var byteCount = (width * height)/3; var lastStr=byteCount-width; var dataArr; var dataStr; var indArr = 0; var dataArrLen = 0; var byteCounter = 0; var newStr = 0; var sendTyp=0; document.addEventListener('DOMContentLoaded', function() { btn.addEventListener('click', function() { connectCom(function(vector) { drawImg(vector); }); }); dev.addEventListener('click', function(){ var win = gui.Window.get(); win.showDevTools(); }); }); function drawImg(imgArr) { var imgData = ctx.createImageData(width, height); var ind; for (var i = 0; i < imgArr.length; i++) { imgData.data[4 * i] = imgArr[i]; imgData.data[4 * i + 1] = imgArr[i]; imgData.data[4 * i + 2] = imgArr[i]; imgData.data[4 * i + 3] = 255; if(i<byteCount&&i>lastStr){ //red line imgData.data[4 * i] = 255; imgData.data[4 * i + 1] = 0; imgData.data[4 * i + 2] = 0; imgData.data[4 * i + 3] = 255; } if(i<2*byteCount&&i>byteCount+lastStr){ //green line imgData.data[4 * i] = 0; imgData.data[4 * i + 1] = 255; imgData.data[4 * i + 2] = 0; imgData.data[4 * i + 3] = 255; } if(i<3*byteCount&&i>2*byteCount+lastStr){ //blue line imgData.data[4 * i] = 0; imgData.data[4 * i + 1] = 0; imgData.data[4 * i + 2] = 255; imgData.data[4 * i + 3] = 255; } } ctx.putImageData(imgData, 0, 0); imgArr.length=0; } function connectCom(callback) { const PIXTYPE=0,BINTYPE=1,FIGTYPE=2; var imgTyp=PIXTYPE; var serialport = require('serialport'); var imgArr = []; var framCount=0,strNum,colNum; var pix=false; var comm = 'COM' + select_com.value; var boudrate = +bdr.value; var SerialPort = serialport.SerialPort; var port = new SerialPort(comm, { baudRate: boudrate, dataBits: 8, stopBits: 1, parity: "none", bufferSize: 65536, parser: SerialPort.parsers.byteLength(1) }); port.on('open', function() { console.log('Port ' + comm + ' Open'); }); port.on('data', function(data) { if(imgTyp==PIXTYPE||imgTyp==BINTYPE){ if (data[0] == 42 && newStr == 0) { newStr = 1; data[0]=255; } if (newStr == 1 && data[0] == 42) { newStr = 2; } if (newStr == 2 && byteCounter <2*byteCount) { colNum=byteCounter%width; strNum=(byteCounter-colNum)/width; if(strNum%2==0){ imgArr[(strNum/2)*width+colNum]=data[0]; } if(strNum%2==1){ imgArr[((strNum-1)/2)*width+byteCount+colNum]=data[0]; } byteCounter++; } if (newStr == 2 && byteCounter == 2*byteCount) { newStr = 0; byteCounter = 0; framCount++; console.log('Frame Num ', framCount); imgTyp=FIGTYPE; } } if(imgTyp==FIGTYPE){ if (data[0] == 42 && newStr == 0) { newStr = 1; data[0]=255; } if (newStr == 1 && data[0] == 42) { newStr = 2; } if (newStr == 2 && byteCounter < byteCount) { imgArr[byteCounter+2*byteCount] = data[0]; byteCounter++; } if (newStr == 2 && byteCounter == byteCount) { newStr = 0; byteCounter = 0; framCount++; console.log('Frame Num ', framCount); imgTyp=PIXTYPE; callback(imgArr); } } }); port.on('error', function() { alert('    '); }); }
      
      







デバイスの操作の例を短いビデオで示します。



プロトタイプNo. 1のビデオ






プロトタイプNo. 2のビデオ




おわりに



得られた結果は、デバイスでの認識方法の効率が高いことを示しています。 いくつかのフレームからの情報を使用して関心のある領域にさらに「ピアリング」する方法に関連する方法を少し改善することにより、認識の品質を市販製品のレベルに上げることができます。



手書き行や象形文字などの複数文字オブジェクトを分析および認識する方法も理解できますが、このためには、esp(512K、250K以上のプログラムボリューム)よりも大きなメモリ容量のデバイスが必要です。

ご清聴ありがとうございました。



参照:



1. ABBYY FineReaderでのテキスト認識(2/2)

2. Omega2:LinuxとWi-Fiを搭載した世界最小のマイクロコンピューター

3. Orange Pi 2G-IoT-IoTに最適なシングルボード

4. マイクロコントローラーでの数字認識

5. OV7670カメラを使用するためのArduinoスケッチ

6. データシートOV7670カメラ

7. ACSモデルでのダイナミクスの反映



All Articles