GIS奮闘記

元GISエンジニアの技術紹介ブログ。主にPythonを使用。

スポンサーリンク

Python で PostgreSQL を操作してみよう!

さて、本日は Python を使った PostgreSQL の操作について紹介しようと思います。

関連記事

Python で SQL Server、SQLite を操作する方法についても書いていますので、興味のある方はぜひ読んでみてください。

www.gis-py.com

www.gis-py.com

使用するライブラリ

psycopg2 というライブラリを使用します。

実行環境

Windows10 64bit
Python 3.6.6
PostgreSQL 12.4

使用するデータ

以下のようなテーブルを用意しました。

f:id:sanvarie:20200901144003p:plain

データは以下のようなものを用意しました。

f:id:sanvarie:20200901144032p:plain

今回試すこと

psycopg2 を使用して以下を実行してみます

ログイン
検索
更新
挿入
削除

ログイン

接続文字列を作成して psycopg2.connect を実行すればログインができます。

# -*- coding: utf-8 -*-
import psycopg2

def connect():
    con = psycopg2.connect("host=" + "localhost" +
                           " port=" + "5432" +
                           " dbname=" + "DB名" +
                           " user=" + "ユーザー" +
                           " password=" + "パスワード")

    return con
if __name__ == '__main__':
    con = connect()

検索

テーブルを検索してカラムの値を print します。

def select_execute(con, sql):
    with con.cursor() as cur:
        cur.execute(sql)
        rows = cur.fetchall()

    return rows

if __name__ == '__main__':
    con = connect()

    sql =  'select * from test'

    res = select_execute(con, sql)
    for r in res:
        print(r[0])
        print(r[1])

このような結果になりました。

f:id:sanvarie:20200901143407p:plain

更新

name カラムの中身を「A」から「B」となるようにデータを更新します。

def updatet_execute(con, slq):
    with con.cursor() as cur:
        cur.execute(sql)

    con.commit()

if __name__ == '__main__':
    con = connect()

    sql =  """update test set name = 'B'"""

    # データ更新
    updatet_execute(con, sql)

想定通りの結果になりました。

f:id:sanvarie:20200901150408p:plain

挿入

1レコード挿入する処理を書いています。

def insert_execute(con, slq):
    with con.cursor() as cur:
        cur.execute(sql, (2,'Z'))

    con.commit()

if __name__ == '__main__':
    con = connect()

    sql =  """insert
                into test(id,
                          name)
                      values(%s,
                             %s)"""

    # データ登録
    insert_execute(con, sql)

ちゃんと挿入した分のレコードが DB 側に反映されていました。

f:id:sanvarie:20200901145114p:plain

削除

上記処理で挿入したレコードを削除しています。

def delete_execute(con, slq):
    with con.cursor() as cur:
        cur.execute(sql)

    con.commit()

if __name__ == '__main__':
    con = connect()

    sql =  """delete
                from test
               where id = 2"""

    # データ削除
    delete_execute(con, sql)

想定通りレコードが削除されました。

f:id:sanvarie:20200901145412p:plain

まとめ

psycopg2 を使った一通りの処理を紹介したのですが、簡単に Python で PostgreSQL を操作できることがわかるかと思います。システム開発にDB はつきものなので、今後も色々 DB の扱いについて書いてみようと思います。本日は以上です。

Python で ini ファイルを読み込んでみよう!

色々忙しくてしばらく更新が止まっていましたが、今日は久しぶりに書いてみようと思います。本日は Python で ini ファイルを読み込む方法について紹介しようと思います。

ini ファイルとは

一言でいうと設定ファイルですね。プログラムで使用する値を ini ファイルに記載します。

使用するライブラリ

configparser という Python 標準ライブラリを使用します。

使用する ini ファイル

以下のような ini ファイルを用意しました。「workspace」と「data」に対してそれぞれ設定値を記載しています。

f:id:sanvarie:20200901103243p:plain

実行環境

Windows10 64bit
Python 3.6.6

サンプル

ini ファイルを読み込んで print で出力するサンプルです。

# -*- coding: utf-8 -*-
import os
from pathlib import Path
import configparser

dic = {}

def read_ini():
    config = configparser.ConfigParser()
    path = os.path.join(Path(__file__).parent, "example.ini")
    config.read(path)
    config.optionxform = str

    for section in config.sections(): # ini ファイルの値をディクショナリに格納
        for key in config.options(section):
            dic[key] = config.get(section, key)

def get_value():
    print(dic["workspace"])
    print(dic["data"])

if __name__ == '__main__':
    read_ini()
    get_value()

configparser を使用して ini ファイルの値をディクショナリに格納して、その値を print しています。結果は想定通り以下のようになりました。

f:id:sanvarie:20200901104502p:plain

まとめ

何かプログラムを作る際、ini ファイルなどを使用することはよくあるかと思います。プログラム内にパスをべた書きするよりも今回のように設定ファイルに記載しておくことをお勧めします。すごく簡単にできるのでぜひ使ってみてください!

TypeScript を使って Web マップにカスタムウィジェットを配置してみよう!

さて、本日は TypeScript を使って Web マップにカスタムウィジェットを配置してみようと思います。みなさんは TypeScript を使っていますでしょうか?おそらく日本ではまだ馴染みのない言語の一つかもしれません。ただ、近年人気が高まってきており、近い将来はメジャーな言語の一つになるのではないかと思っています。

TypeScript とは

TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーでオープンソースのプログラミング言語である。TypeScript は JavaScriptに対して、省略も可能な静的型付けとクラスベースオブジェクト指向を加えた厳密なスーパーセットとなっている(Wikipedia より抜粋)。

簡単に言うと JavaScript の進化版といったところでしょうか。そして、TypeScript で書いたコードをコンパイルすると JavaScript のコードに変換することができます。もっと細かいことを知りたい方は以下 Web サイトに記載されているので、読んでみてください。

qiita.com

なぜ TypeScript を使ってみようと思ったのか

以下のように JavaScript → TypeScript への流れに取り残されないようにするためでしょうか。やはりエンジニアの端くれとして新しいことにチャレンジし続けたいですね。

  1. Google が TypeScript を社内の標準言語に採用するなど、TypeScript の存在感は年々高まってきているため
  2. 米国Esri が TypeScript を推しているため(将来的には 全てのコードが TypeScript になるかも?!)
  3. 個人的に将来を見据えて新しいことに挑戦したかったため

準備

まずは環境を準備しなくてはいけませんね。こちらは ESRIジャパンさんのブログなのですが、TypeScript の環境構築から簡単なサンプルコードを載せてくれています。こちらを参考にさせてもらいます。

community.esri.com

また、TypeScript でのカスタムウィジェット作成に関しては、ESRIジャパンさんが公開しているカスタムウィジェット ハンズオンを参考にしてみました。

github.com

今回チャレンジすること

概観図をカスタムウィジェットとして作ってみようと思います。概観図とは以下の画像の右上にある地図のことです。メインのマップの概観を表示させるものですね。

f:id:sanvarie:20200517151318p:plain

概観図の JavaScript 用のサンプルコードが以下に公開されているので、今回はこれを TypeScript に書き換えてみようと思います。 developers.arcgis.com

作ったもの

上記に記載したリソースをもとに以下のような構成でカスタムウィジェットつきのWeb マップを作ってみました。

f:id:sanvarie:20200517151959p:plain

サンプルコード

「OverView.tsx」「index.html」のサンプルコードです。

OverView.tsx

import Map from "esri/Map";
import MapView from "esri/views/MapView";
import Graphic from "esri/Graphic";
import * as watchUtils from "esri/core/watchUtils";
import {subclass, declared, property} from "esri/core/accessorSupport/decorators";
import Widget = require("esri/widgets/Widget");
import { renderable, tsx } from "esri/widgets/support/widget";

@subclass("esri.widgets.OverView")
class OverView extends declared(Widget) {

  constructor() {
    super();
    this._overViewChange = this._overViewChange.bind(this);
  }

  @property()
  @renderable()
  map: Map;

  @property()
  @renderable()
  mainView: MapView;

  @property()
  @renderable()
  mapView: MapView;

  @property()
  @renderable()
  graphic: Graphic;
  
  // postInitialize メソッドの追加
  postInitialize() {

    var overviewMap = new Map({
      basemap: "topo"
    });

    this.mapView = new MapView({
      container: "overviewDiv",
      map: overviewMap,
      center: [139.740286, 35.678601],
      zoom: 10,
      constraints: {
        rotationEnabled: false
      }
    });

    this.mapView.ui.components = [];
    
    var graphicSymbol = {
      type: "simple-fill",
      color: [100, 0, 0, 0.5]
    };
  
    this.graphic = new Graphic({
      geometry: null,
      symbol: graphicSymbol
    });
    
    this.mapView.graphics.add(this.graphic);

    watchUtils.init(this, "mainView.extent", () => this._overViewChange());
  }

  private _overViewChange () {
    if (this.mainView.stationary) {
      this.mapView
        .goTo({
          center: this.mainView.center,
          scale:
          this.mainView.scale *
            2 *
            Math.max(
              this.mainView.width / this.mapView.width,
              this.mainView.height / this.mapView.height
            )
        })
        .catch(function(error) {
          if (error.name != "view:goto-interrupted") {
            console.error(error);
          }
        });
    }

    this.graphic.geometry = this.mainView.extent;
  }

  render() {
    return (
      <div
      bind={this}>
      </div>
    );
  }
}

export = OverView;

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="概観図"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <title>Overview map - 4.15</title>
    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }

      #overviewDiv {
        position: absolute;
        top: 12px;
        right: 12px;
        width: 600px;
        height: 200px;
        border: 1px solid black;
        z-index: 1;
        overflow: hidden;
      }
    </style>

    <link
      rel="stylesheet"
      href="https://js.arcgis.com/4.15/esri/themes/light/main.css"
    />
    <script>
    /*
    dojoConfig の設定
    */
    var locationPath = location.pathname.replace(/\/[^\/]+$/, "");
    window.dojoConfig = {
        packages: [
        {
            name: "app",
            location: locationPath + "/app"
        }
        ]
    };
    </script>
    <script src="https://js.arcgis.com/4.15/"></script>

    <script>
      require([
        "esri/Map",
        "esri/views/MapView",
        "app/OverView"
      ], function(Map, MapView, OverView) {
        
        var mainMap = new Map({
          basemap: "hybrid"
        });


        var mainView = new MapView({
          container: "viewDiv",
          map: mainMap,
          center: [139.740286, 35.678601],
          zoom: 15
        });

        mainView.when(function() {
          /*
          ウィジェットのインスタンス化
          */

          overView = new OverView({
            map:mainMap,
            mainView:mainView
          });

      mainView.ui.add(overView, "top-right");
      });
    });
    </script>
  </head>
  <body>
    <div id="viewDiv" class="esri-widget"></div>
    <div id="overviewDiv"></div>
  </body>
</html>

TypeScript に関してはほとんど素人同然なので変な書き方をしているかもしれませんが、完成させることができたのでひとまずほっとしております。

以下のように概観図がマップに配置されていることが確認できました。

f:id:sanvarie:20200517153044p:plain

メインのマップを移動させると概観図の赤枠(メインのマップの表示箇所)も移動することがわかります。

f:id:sanvarie:20200517153136p:plain

TypeScript を使ってみた感想

最初は書き方や TypeScript 独特のコンセプトに戸惑うこともあったのですが、慣れの問題かと思います。色々使ってみる中で感じたのは、やはり型が使えるというのが JavaScript に比べて大きなアドバンテージかなと思いました(今回はあまりそのメリットを享受していないのですが)。まだまだ知らないことも多いのですが、色々触りながら勉強してみようと思います。本日は以上です。

Python で XML を CSV に変換する方法

さて、本日はPython で XML を CSV に変換する方法について書いてみようと思います。前回のエントリーでは Python で CSV を XML に変換する方法について書きましたが、今回はその逆ですね。

使用する XML

前回のエントリーで作成した prefectures.xml を使用します。興味のある方はぜひ前回のエントリーを読んでみてください。

f:id:sanvarie:20200411132917p:plain

www.gis-py.com

出力する CSV

以下のような形で CSV を出力しようと思います。

f:id:sanvarie:20200411121747p:plain

実行環境

Windows 10 64bit
Python 3.6.6

サンプルコード

prefectures.xml を CSV に変換するサンプルです。

# -*- coding: utf-8 -*-
import csv
from xml.etree import ElementTree

def xml2csv():
    input_file = r"D:\data\csv\prefectures_from_xml.csv"
    output_file = r"D:\data\csv\prefectures.xml"

    # CSV 作成
    f = open(input_file, "w")

    # ヘッダー
    output_string = "公表年,公表月,公表日,都道府県,患者数(2020年3月28日からは感染者数),現在は入院等,退院者,死亡者\n"

    # ヘッダー書き込み
    f.write(output_string)

    # XML 読込
    tree = ElementTree.parse(output_file)

    # ルート
    root = tree.getroot()

    text_list = []

    for r in root:

        if r.tag == "date":
            text_list.append(r.find('year').text)
            text_list.append(r.find('month').text)
            text_list.append(r.find('day').text)

        elif r.tag == "prefecture":
            text_list.append(r.text)

        elif r.tag == "info":
            text_list.append(r.find('cases').text)
            text_list.append(r.find('hospitalized').text)
            text_list.append(r.find('released').text)
            text_list.append(r.find('death').text)

        if len(text_list) == 8:
            # CSV に書き込み
            output_string = text_list[0] \
                            + "," + text_list[1] \
                            + "," + text_list[2] \
                            + "," + text_list[3] \
                            + "," + text_list[4] \
                            + "," + text_list[5] \
                            + "," + text_list[6] \
                            + "," + text_list[7] + "\n"

            f.write(output_string)

            # リストをクリア
            text_list = []

    f.close

if __name__ == '__main__':
    xml2csv()

結果

想定通りの形で CSV が出力されました。

f:id:sanvarie:20200411150128p:plain

さいごに

今回はさくっと終わらせることができました。前回のエントリーで CSV→XML、今回のエントリーでXML→CSV に変換する方法を紹介しました。これで相互変換が必要になっても大丈夫かと思います。前回も使用しましたが、今回も ElementTree を使用しています。Python で XML を扱う際はよく使用するライブラリかと思いますので、ぜひ色々使ってみてください。今回は以上です。

Python で CSV を XML に変換する方法

さて、本日は Python で CSV を XML に変換する方法について書いてみようと思います。自粛ムードが高まってきていてなかなか外出はできないかと思いますが、休日にコーディングをするチャンスでもありますね。自分ももっと勉強しなければならないので、これを機にコーディングする時間を増やせればと思います。

使用する CSV

前回エントリーで使用した prefectures.csv を使ってみようと思います。興味のある方はぜひ前回のエントリーを読んでみてください。

f:id:sanvarie:20200411121747p:plain

www.gis-py.com

出力する XML

以下のような形で XML を出力しようと思います。

<root>
    <date>
        <year>2020</year>
        <month>4</month>
        <day>11</day>
    </date>
    <prefecture>東京都</prefecture>
    <info>
        <cases>500</cases>
        <hospitalized>100</hospitalized>
        <released>200</released>
        <death>10</death>
    </info>
</root>

使用するライブラリ

Pandas と ElementTree を使用します。

実行環境

Windows10 64bit
Python 3.6.6

サンプルコード

prefectures.csv を XML に変換するサンプルです。

# -*- coding: utf-8 -*-
import pandas as pd
import xml.etree.ElementTree as et

def csv2xml():
    input_file = r"D:\data\csv\prefectures.csv"
    output_file = r"D:\data\csv\prefectures.xml"

    # CSV 読込
    df = pd.read_csv(input_file)

    # ルートを作成
    root = et.Element("root")
    for i, row in df.iterrows():

        # 公表年、公表月、公表日の要素を作成
        child_root_date = et.SubElement(root, "date")
        year = et.SubElement(child_root_date, 'year')
        month = et.SubElement(child_root_date, 'month')
        day = et.SubElement(child_root_date, 'day')

        # 都道府県の要素を作成
        child_root_prefecture = et.SubElement(root, "prefecture")

        # 患者数(2020年3月28日からは感染者数)、現在は入院等、退院者、志望者の要素を作成
        child_root_info = et.SubElement(root, "info")
        cases = et.SubElement(child_root_info, 'cases')
        hospitalized = et.SubElement(child_root_info, 'hospitalized')
        released = et.SubElement(child_root_info, 'released')
        death = et.SubElement(child_root_info, 'death')

        # 各要素に値をセットする
        for iCol, column in df.iteritems():

            if iCol == "公表年":
                year.text = str(row[iCol])
            elif iCol == "公表月":
                month.text = str(row[iCol])
            elif iCol == "公表日":
                day.text = str(row[iCol])
            elif iCol == "都道府県":
                child_root_prefecture.text = str(row[iCol])
            elif iCol == "患者数(2020年3月28日からは感染者数)":
                cases.text = str(row[iCol])
            elif iCol == "現在は入院等":
                hospitalized.text = str(row[iCol])
            elif iCol == "退院者":
                released.text = str(row[iCol])
            elif iCol == "死亡者":
                death.text = str(row[iCol])

    # XMLを出力
    et.ElementTree(root).write(output_file, encoding="utf-8")

if __name__ == '__main__':
    csv2xml()

結果

想定通りの形で XML が出力されました。

f:id:sanvarie:20200411132917p:plain

さいごに

最初は少してこずったのですが、それ以外は意外とすんなりできました。ただ、もっとスマートなやり方があるような気がしてなりません。まだまだ勉強不足ですね。CSV から XML に変換するということはそんなに頻繁に行うことではないと思いますが、もし興味がある場合はぜひ試してみてください。また、今回のケースに限らず Pandas と ElementTree はよく使うライブラリですので、Python を勉強している、もしくは、勉強したいという方は別のケースでもぜひ使ってみてください。本日は以上です。

ArcGIS Pro を使ってコロナウイルスの感染拡大状況を時系列でみる方法

さて、大変な世の中になってきましたね。コロナウイルスが猛威を振るっていますが、今回はその拡大状況を可視化してみようと思います。実現するには色々な方法がとれると思うのですが、私は ArcGIS Pro を一押しします。

使用するデータ

①コロナウイルス関連のデータ
東洋経済オンラインさんが github で感染状況を CSV として公開していますので、こちらを使用させてもらいます。

github.com

公開されている CSV の中身

  1. data.json・・・json で公開しているデータ
  2. demography.csv・・・年代ごとの感染者の統計
  3. prefectures-2.csv・・・日ごとの各都道府県の陽性者数と検査人数の累計
  4. prefectures.csv・・・日ごとの各都道府県の患者数(2020年3月28日からは感染者数)の累計など
  5. summary.csv・・・日ごとのPCR検査陽性者の累計など

※今回はprefectures.csvを使用します。

②日本地図
ESRIジャパンさんが公開している「全国市区町村界データ」を使用します。

www.esrij.com

実行環境

Windows 10 64 bit
ArcGIS Pro 2.4

データ作成手順

  1. prefectures.csv を FGDB にインポート
  2. 「1」のデータを加工
  3. 全国市区町村界データを FGDB にインポート
  4. 全国市区町村界データを加工
  5. 「2」「4」のデータをテーブル結合
  6. 「5」のデータに対してシンボル設定

1. prefectures.csv を FGDB にインポート

テーブル → テーブルのジオプロを使用します。

f:id:sanvarie:20200409202213p:plain

テーブルが作成されました。 f:id:sanvarie:20200409202252p:plain

2. 「1」のデータを加工

当該データには「公表年」「公表月」「公表日」というカラムがありますが、これらを一つのカラムにまとめます。

①「年月日」という日付型のカラムを作成します。

f:id:sanvarie:20200409202715p:plain

②フィールド演算で「公表年」「公表月」「公表日」カラムの値を「年月日」カラムにセットします

f:id:sanvarie:20200409202737p:plain

③「年月日」カラムに値が設定されていることを確認してください

f:id:sanvarie:20200409202800p:plain

3. 全国市区町村界データを FGDB にインポート

フィーチャークラス → フィーチャークラスのジオプロを使用します。

f:id:sanvarie:20200409203105p:plain

全国市区町村界データがインポートされました

f:id:sanvarie:20200409203137p:plain

4. 全国市区町村界データを加工

当該データは市区町村単位なので、県ごとのポリゴンに加工します。ディゾルブのジオプロを使用します。

f:id:sanvarie:20200409203502p:plain

県ごとのポリゴンになりました。

f:id:sanvarie:20200409203530p:plain

以下エントリーで同じことを ArcPy で実行しています。興味のある方はぜひ読んでみてください。

www.gis-py.com

5. 「2」「4」のデータをテーブル結合

テーブル結合のジオプロを使用します。

f:id:sanvarie:20200409204017p:plain

二つのデータが結合されました。

f:id:sanvarie:20200409204129p:plain

6. 「5」のデータに対してシンボル設定

「5」のデータに対して以下のように等級色でシンボルを設定します。

f:id:sanvarie:20200409220634p:plain

作成したデータを時系列でみる手順

  1. 作成したデータに対して時間設定
  2. 時間の詳細設定

1. 作成したデータに対して時間設定

作成したデータ(レイヤー)を右クリック>プロパティをクリックしてください。

f:id:sanvarie:20200409220726p:plain

時間タブを以下の画像のように設定します。

f:id:sanvarie:20200409204939p:plain

2. 時間の詳細設定

時間タブ内の設定を以下の画像のようにします。

f:id:sanvarie:20200409230437p:plain

このような感じで感染が拡大していく様子がわかります。岩手県など、感染者数がゼロ(2020/4/6時点)の県もあるようですね。

f:id:sanvarie:20200409230040g:plain

薄い青・・・感染者数が少ない県
濃い青・・・感染者数が多い県

さいごに

ArcGIS Pro を使えば、簡単にコロナウイルスの感染拡大状況を時系列でみることができます。東洋経済オンラインさんが公開している他のデータを利用すれば、より詳細にデータを可視化・分析することも可能ですね。今後、ArcGIS Pro だけではなく、他の手段でも同じことができるか試してみようと思います。本日は以上です。

Python と Tesseract OCR を使って文字認識をしてみよう!

本日は Python と Tesseract OCR を使って文字認識をしてみようと思います。みなさんは OCR と聞いてピンときますか?実は私たちの周りは OCR を使用したテクノロジーで溢れかえっています。

OCR とは?

OCR(Optical Character Recognition/Reader、オーシーアール、光学的文字認識)とは、手書きや印刷された文字を、イメージスキャナやデジタルカメラによって読みとり、コンピュータが利用できるデジタルの文字コードに変換する技術のことです。この技術を利用することによって、例えば、紙に書かれている情報を毎回パソコンで手入力しなければいけない、ということはよくあるかと思います。OCR を使うことによってこの作業を自動化することができるようになります。

最近はフリーの OCR エンジン が普及していており、プログラミング で OCR を扱うことが容易になってきました。

Tesseract OCR とは?

Google が公開したオープンソースの文字認識エンジンです。こんな便利なものを公開してくれるなんて本当にありがたいですね。

Pythonで Tesseract OCRを扱うためには?

PyOCR というライブラリを使用します。pip install pyorc でダウンロードできます。

Tesseract OCR を Windows にインストールしてみる

以下のサイトで Windows 用のインストーラーをダウンロードすることができます。32 bit版と64bit 版がありますが、今回は64 bit版をダウンロードします。

github.com

インストール方法は以下のサイトを参考にしてみました。

hei.techblog.jp

環境変数の設定

環境変数にTesseract OCR のパスをセットします。「C:\Program Files\Tesseract-OCR」のような感じで追加してください(ここの設定は Tesseract OCR のインストール先によります)。

※この手順は必須ではありません。

Tesseract OCR を使ってみる

インストール後、コマンドプロンプト上 で Tesseract OCR を実行することができるようになります。

使用するデータ

前回のエントリーの一部を画像化してみました。

f:id:sanvarie:20200322105124p:plain

エントリーの内容に興味のある方はぜひ読んでみてください。

www.gis-py.com

実行方法

tesseract D:\mesh.png D:\mesh-out -l jpn

のような感じでコマンドを実行します。-l jpn は日本語で認識させるためのオプションです。

実行後、以下のようなテキストが出力されます。多少の誤認識はありますが、大体の文字はきちんと認識されていることがわかります。

f:id:sanvarie:20200321201933p:plain

ページセグメンテーションモード -psm

文字の配置の仕方は画像によって様々かと思います。ページセグメンテーションモード を指定することによってより正確に文字が認識される可能性があります。デフォルトでは psm 3が指定されます。

0 Orientation and script detection (OSD) only.
1 Automatic page segmentation with OSD.
2 Automatic page segmentation, but no OSD, or OCR.
3 Fully automatic page segmentation, but no OSD. (Default)
4 Assume a single column of text of variable sizes.
5 Assume a single uniform block of vertically aligned text.
6 Assume a single uniform block of text.
7 Treat the image as a single text line.
8 Treat the image as a single word.
9 Treat the image as a single word in a circle.
10 Treat the image as a single character.
11 Sparse text. Find as much text as possible in no particular order.
12 Sparse text with OSD.
13 Raw line. Treat the image as a single text line, bypassing hacks that are Tesseract-specific.

例えば、画像を1行のテキストとして扱いたい場合は以下のようにします。

tesseract D:\mesh.png D:\mesh-out -l jpn --psm 7

PyOCR を使ってみる

それでは準備が整いましたので、PyOCR を使ってみようと思います。

実行環境

Windows 10 64bit
Pyton 3.6.6
Tesseract OCR 5.0.0
PyOCR 0.7.2

サンプルコード

読み込んだ画像の文字をテキストにして出力するサンプルです。

# -*- coding: utf-8 -*-
from PIL import Image
import pyocr

# OCR エンジン取得
tools = pyocr.get_available_tools()
tool = tools[0]

# 使用する画像を指定してOCRを実行
txt = tool.image_to_string(
    Image.open(r"D:\mesh.png"),
    lang="jpn",
    builder=pyocr.builders.TextBuilder()
)

# 結果をテキストで出力
with open(r"D:\ocr-result.txt", "w") as f:
    print(txt, file=f)

実行後、テキストが出力されたことが確認できました。

f:id:sanvarie:20200321220711p:plain

画像の一部のみを対象にして処理を実行する

実際に業務でこのような技術を使用する場合、認識させたいのは一部の箇所だけというケースは多いかと思います。今回はその方法も紹介します。

抽出する箇所

赤枠で囲った箇所を抽出します。

f:id:sanvarie:20200322102643p:plain

画像の特定の箇所を指定する方法

座標(左上のXY, 右下のXY)を指定することで抽出する箇所を設定します。 座標の確認はペイント上で行うことができます。

f:id:sanvarie:20200322102836p:plain

赤枠の箇所を指定する場合、以下の座標を設定する必要があります。

f:id:sanvarie:20200322103049p:plain

サンプルコード

一部の箇所を抽出したい場合は crop() メソッドを使用します。最初のサンプルとの違いはそこだけです。簡単ですね。

# -*- coding: utf-8 -*-
from PIL import Image
import pyocr

# OCR エンジン取得
tools = pyocr.get_available_tools()
tool = tools[0]

# 使用する画像を指定してOCRを実行
txt = tool.image_to_string(
    Image.open(r"D:\mesh.png").crop((20,380, 130,410)),
    lang="jpn",
    builder=pyocr.builders.TextBuilder()
)

# 結果をテキストで出力
with open(r"D:\ocr-result.txt", "w") as f:
    print(txt, file=f)

結果を確認すると狙った箇所がしっかり認識されていることがわかります。

f:id:sanvarie:20200322103219p:plain

最後に

今回、Tesseract OCR を色々使ってみたのですが、すごく簡単に使えるのに機能的にも非常に優秀な OCR エンジンだなという印象を受けました。ただ、実際に業務で使うといった場合は課題も多いのではないかと思いました。例えば、「緯度」を「旨度」と認識してしまったり、数字の扱いもあまり得意ではないことがわかりました。この辺を工夫する必要があるかと思います。また、手書きの文字を認識するのも難しいです。そのため、認識率を上げるには機械学習などと組み合わせて使用する必要が出てくるかと思います。使い方次第ではあるのですが、うまく使いこなせばすごく面白いことができそうですね。興味のある方はぜひ使ってみてください。本日は以上です。