workshop/CV/main

画像処理/光学文字認識/ストローク認識

はじめに

まずはOpenframeworksの準備をします.

  1. Mac OSXが動くコンピュータ
  2. Xcode
  3. App Storeからダウンロード&インストールして下さい.Openframeworks ではOS10.6用のSDKを利用しているので, ここからダウンロードして,/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs の中にMacOSX10.6.sdkをコピーしてください.
  4. Openframeworks
  5. OpenframeworksのDownloadページからOSX用のXcodeと書かれた ファイルをダウンロードして下さい.ダウンロードが終わったら適当な場所に解凍し,これで準備完了です.
  6. apps/myApps/emptyExample/emptyExample.xcodeproj をダブルクリックして開く
  7. 下の画像にあるRunボタンを押して,画面(特になにも表示されない)が出現すればOK

以上で準備は終わりになります.examples フォルダにサンプルがたくさんあるのでいくつか実行して, どんなことができるのか見てみましょう.

ofxOpenCv

ofxOpenCvはOpenframeworksにバンドルされているアドオンになります.まずはexampleを幾つか試してください. ここでは実際に examples/addons/opencvExample をひな形にいくつかの機能を試してみましょう.

ではexample/addons/opencvExampleをmyAppsフォルダにコピーして,これを編集しながら実践してみましょう

なにもしないプログラムに戻す

opencvExampleにはいろいろ書いてありますが,まずは全部消して,なにもしないプログラムにします.

test.cpp
#include "testApp.h"
void testApp::setup(){
}
void testApp::update(){
}
void testApp::draw(){
}
void testApp::keyPressed(int key){
}
test.h
#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
class testApp : public ofBaseApp{
	public:
		void setup();
		void update();
		void draw();
		void keyPressed(int key);
};

カメラから画像を読み込んで表示する

では,カメラから画像を取得し,それをウィンドウ内に表示してみましょう.

test.cpp
#include "testApp.h"
void testApp::setup(){
 vidGrabber.initGrabber(320,240);
}
void testApp::update(){
  vidGrabber.update();
  vidGrabber.grabFrame();
}
void testApp::draw(){
  ofSetColor(255,255,255);
  vidGrabber.draw(0,0);
}
void testApp::keyPressed(int key){
}
test.h
#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
class testApp : public ofBaseApp{
	public:
		void setup();
		void update();
		void draw();
		void keyPressed(int key);
		ofVideoGrabber  vidGrabber;

};

となりに白黒画像を表示してみる

カラーの表示の横に,モノクロのカメラ画像を表示してみます.

test.cpp
#include "testApp.h"
void testApp::setup(){
  vidGrabber.initGrabber(320,240);
	ofimage_gray.allocate(320,240, OF_IMAGE_COLOR);
	ofxcvimage_gray.allocate(320,240);
}
void testApp::update(){
  vidGrabber.update();
  vidGrabber.grabFrame();
	ofimage_gray.setFromPixels(vidGrabber.getPixels(),320,240, OF_IMAGE_COLOR);
	ofimage_gray.setImageType(OF_IMAGE_GRAYSCALE);
	ofxcvimage_gray.setFromPixels(ofimage_gray.getPixels(), 320, 240);
}
void testApp::draw(){
  ofSetColor(255,255,255);
  vidGrabber.draw(0,0);
  ofxcvimage_gray.draw(320,0);
}
void testApp::keyPressed(int key){
}
test.h
#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
class testApp : public ofBaseApp{
public:
	void setup();
	void update();
	void draw();
	void keyPressed(int key);
	ofVideoGrabber  vidGrabber;
	ofImage ofimage_gray;
	ofxCvGrayscaleImage  ofxcvimage_gray;
};

二値化画像を表示する

カラーの表示の下に,二値化画像(白黒)を表示します.最初は80(0-255)の値を 入れ,キーボード入力の'+', '-'に応じて閾値(しきいち)を増やしたり減らしたりして, 画像の変化を確認してみます

test.cpp
#include "testApp.h"
void testApp::setup(){
  vidGrabber.initGrabber(320,240);
	ofimage_gray.allocate(320,240, OF_IMAGE_COLOR);
	ofxcvimage_gray.allocate(320,240);
	ofxcvimage_binary.allocate(320, 240);
	value_threshold = 80;
}
void testApp::update(){
  vidGrabber.update();
  vidGrabber.grabFrame();
	ofimage_gray.setFromPixels(vidGrabber.getPixels(),320,240, OF_IMAGE_COLOR);
	ofimage_gray.setImageType(OF_IMAGE_GRAYSCALE);
	ofxcvimage_gray.setFromPixels(ofimage_gray.getPixels(), 320, 240);
	ofxcvimage_binary.setFromPixels(ofimage_gray.getPixels(), 320, 240);
	ofxcvimage_binary.threshold(value_threshold);
}
void testApp::draw(){
  ofSetColor(255,255,255);
  vidGrabber.draw(0,0);
  ofxcvimage_gray.draw(320,0);
  ofxcvimage_binary.draw(0,240);
}
void testApp::keyPressed(int key){
	if( key == '+'){
		value_threshold = value_threshold + 10;
	}
	else if( key == '-' ){
		value_threshold = value_threshold - 10;
	}
}
test.h
#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
class testApp : public ofBaseApp{
public:
	void setup();
	void update();
	void draw();
	void keyPressed(int key);
	ofVideoGrabber  vidGrabber;
	ofImage ofimage_gray;
	ofxCvGrayscaleImage ofxcvimage_gray;
	ofxCvGrayscaleImage ofxcvimage_binary;
	int value_threshold;
};

二値化画像をスライダーで制御する

ofxUIというアドオンを利用して,画面にスライダー(ウィジェット)を取り付けます. そのスライダーを左右に移動させることで,動的に閾値を変化させてみます.アドオンを 追加できたら下記プログラムを記述します.なお,ofxUIはofxXmlSettingsも必要になるので 忘れずにこちらも追加してください.ofxUIのexampleフォルダにあるGUIフォルダ一式を bin/data の中にコピーするのを忘れずに.

ofxUIのダウンロードページへ

test.cpp
#include "testApp.h"
void testApp::setup(){
  vidGrabber.initGrabber(320,240);
  ofimage_gray.allocate(320,240, OF_IMAGE_COLOR);
  ofxcvimage_gray.allocate(320,240);
  ofxcvimage_binary.allocate(320, 240);
  value_threshold = 80;
  gui = new ofxUICanvas(320*2,0,1024-320*2,768);
  gui->addWidgetDown(new ofxUILabel("503 Lesson", OFX_UI_FONT_LARGE));
  gui->addWidgetDown(new ofxUISlider(200,20,0.0,255.0,100.0,"Threshold"));
  ofAddListener(gui->newGUIEvent, this, &testApp::guiEvent);
  gui->loadSettings("GUI/guiSettings.xml");
}
void testApp::update(){
  vidGrabber.update();
  vidGrabber.grabFrame();
  ofimage_gray.setFromPixels(vidGrabber.getPixels(),320,240, OF_IMAGE_COLOR);
  ofimage_gray.setImageType(OF_IMAGE_GRAYSCALE);
  ofxcvimage_gray.setFromPixels(ofimage_gray.getPixels(), 320, 240);
  ofxcvimage_binary.setFromPixels(ofimage_gray.getPixels(), 320, 240);
  ofxcvimage_binary.threshold(value_threshold);
}
void testApp::draw(){
  ofSetColor(255,255,255);
  vidGrabber.draw(0,0);
  ofxcvimage_gray.draw(320,0);
  ofxcvimage_binary.draw(0,240);
}
void testApp::keyPressed(int key){
  if( key == '+'){
    value_threshold = value_threshold + 10;
  }
  else if( key == '-' ){
    value_threshold = value_threshold - 10;
  }
}

void testApp::exit()
{
  gui->saveSettings("GUI/guiSettings.xml");
  delete gui;
}

void testApp::guiEvent(ofxUIEventArgs &e)
{
  if(e.widget->getName() == "Threshold"){
    ofxUISlider *slider = (ofxUISlider *) e.widget;
    value_threshold = slider->getScaledValue();
  }
}
test.h
#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
#include "ofxUI.h"
class testApp : public ofBaseApp{
public:
  void setup();
  void update();
  void draw();
  void keyPressed(int key);
  ofVideoGrabber  vidGrabber;
  ofImage ofimage_gray;
  ofxCvGrayscaleImage ofxcvimage_gray;
  ofxCvGrayscaleImage ofxcvimage_binary;
  int value_threshold;
  ofxUICanvas *gui;
  
  void exit();
  void guiEvent(ofxUIEventArgs &e);
};

領域検出(Contour Finder)を利用する

白黒画像を元に領域検知を行い,その場所を画面上で特定します.

test.cpp
#include "testApp.h"
void testApp::setup(){
  vidGrabber.initGrabber(320,240);
  ofimage_gray.allocate(320,240, OF_IMAGE_COLOR);
  ofxcvimage_gray.allocate(320,240);
  ofxcvimage_binary.allocate(320, 240);
  value_threshold = 80;
  gui = new ofxUICanvas(320*2,0,1024-320*2,768);
  gui->addWidgetDown(new ofxUILabel("503 Lesson", OFX_UI_FONT_LARGE));
  gui->addWidgetDown(new ofxUISlider(200,20,0.0,255.0,100.0,"Threshold"));
  ofAddListener(gui->newGUIEvent, this, &testApp::guiEvent);
  gui->loadSettings("GUI/guiSettings.xml");
}
void testApp::update(){
  vidGrabber.update();
  vidGrabber.grabFrame();
  ofimage_gray.setFromPixels(vidGrabber.getPixels(),320,240, OF_IMAGE_COLOR);
  ofimage_gray.setImageType(OF_IMAGE_GRAYSCALE);
  ofxcvimage_gray.setFromPixels(ofimage_gray.getPixels(), 320, 240);
  ofxcvimage_binary.setFromPixels(ofimage_gray.getPixels(), 320, 240);
  ofxcvimage_binary.threshold(value_threshold);
  contour_finder.findContours(ofxcvimage_binary, 20, (340*240)/3, 10, true);
}
void testApp::draw(){
  ofSetColor(255,255,255);
  vidGrabber.draw(0,0);
  ofxcvimage_gray.draw(320,0);
  ofxcvimage_binary.draw(0,240);
  
  vidGrabber.draw(320,240);
  contour_finder.draw(320,240);
}
void testApp::keyPressed(int key){
  if( key == '+'){
    value_threshold = value_threshold + 10;
  }
  else if( key == '-' ){
    value_threshold = value_threshold - 10;
  }
}

void testApp::exit()
{
  gui->saveSettings("GUI/guiSettings.xml");
  delete gui;
}

void testApp::guiEvent(ofxUIEventArgs &e)
{
  if(e.widget->getName() == "Threshold"){
    ofxUISlider *slider = (ofxUISlider *) e.widget;
    value_threshold = slider->getScaledValue();
  }
}
test.h
#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
#include "ofxUI.h"
class testApp : public ofBaseApp{
public:
  void setup();
  void update();
  void draw();
  void keyPressed(int key);
  ofVideoGrabber  vidGrabber;
  ofImage ofimage_gray;
  ofxCvGrayscaleImage ofxcvimage_gray;
  ofxCvGrayscaleImage ofxcvimage_binary;
  int value_threshold;
  ofxUICanvas *gui;
  
  void exit();
  void guiEvent(ofxUIEventArgs &e);
  ofxCvContourFinder contour_finder;
};

ofxOcrad

ofxOcradは馬場がocradライブラリを元にOpenframeworksで簡単に利用できるよう作成したアドオンになります. ofxOcradのダウンロード

これまでofxOpenCvを利用して,カメラ画像を2値化処理し,その中で領域検出を行なうプログラムを作成しました. 次はofxOcradを利用して,検出した領域に対してOCR処理をし,文字を判別します.判別した文字は各検出領域の 横に表示してみます.文字かどうかを判断するわけではないので,見つけた領域を片っ端からOCR処理かけます.

test.cpp

test.h

ofxNHocr

ofxOcradではアルファベットや数字,記号等を識別することができますが,日本語文字を認識することはできません. 日本語文字認識には様々なライブラリがありますが,ここではNHocrを利用してみます.

ofxManualInputPlay

ofxOcradとofxBox2D, ofxRealtimeMidiPlayerを利用して,上記のようなアプリケーションを作成しました.記述した文字を 識別し,それぞれのアルファベットに対し音階を割り当て,マウスからダブルクリックすることで,その文字を box2D内にてオブジェクト化します.

ofxZinnia

これまで記述された画像データを基に文字認識処理を行う方式(オフライン方式)を利用してきました. しかしマウスやペンタブレット等を利用した場合,結果画像データだけでなく,そのあいだの記述データ(ストロークデータ)も 取得することができます.例えば読みの分からない漢字を調べるときに利用する入力方式がオンライン入力になります. オンライン入力を利用することでより高精度な文字認識が可能になります.今回はこれを利用してみましょう.

オンライン文字認識の有名なライブラリにzinniaが挙げられます.これはsvm方式により,ストロークデータから文字候補を 教えてくれるライブラリです.既存の日本語文字の他,登録をすれば自分だけのストロークデータを識別することも可能です. ではまず各アドオンとexampleをダウンロードし,実行してみましょう.マウスで記述したものに対して,どのような文字か を逐次画面に表示します.

ofxAirpen

ペンと超音波センサにて構成されるデバイスで,実際に記述したペン位置等のデータをワイヤレスにコンピュータに送信する機能がついています. airpen内部には台湾のpegatech社製部品が利用されているため,pegatech社が提供するデータシートやSDKを用いてアプリケーションを開発することができます.

pegatech.com SDK


Copyright (c) 2015 Tetsuaki BABA all rights reserved.