【日本語 – 手書き編】直線除去の機能を実装:横書き・縦書き文章の日本語手書き文字検出(ブロック→行と列→個別文字)

【日本語 - 手書き編】直線除去の機能を実装:横書き・縦書き文章の日本語手書き文字検出(ブロック→行と列→個別文字)

 

【動画】日本語 – 手書き編:文字検出&直線除去機能

 

 

【日本語 – 手書き編 #3-2】
文字検出&直線除去:横書き・縦書き(平仮名・片仮名・漢字・ローマ字・点・丸)
– Japanese handwritten character OCR:Text Detection & line-cut


視聴時間:8分41秒

 




 

【動画の内容】

ステップ3-2:OCRプログラミングのための直線検出&除去・文字検出(日本語手書き)
0:00 事前準備
1:02 文字検出&直線検出画像アップロード
1:37 直線検出と除去
4:52 文字検出
8:16 次のステップへ

 

【ステップ3-2:線が入った画像の日本語検出】
Python・OpenCVで直線除去の機能を実装:横書き・縦書き文章の日本語手書き文字検出(ブロック→行と列→個別文字:ひらがな・カタカナ・漢字・ローマ字・、点・。丸)
日本語用のOCRを作成するために、直線除去機能付きの水平・垂直な文章の文字検出プログラムを作成してみましょう。

既存のOCRプログラムで日本語の手書き文字の画像認識をしてみても、思ったよりも上手く行かない経験をされる方もいるのではないかと思います。
一連のプログラムが、これから、日本語をはじめとしたOCRに挑戦したい方の参考になることがありましたら幸いです。

Japanese handwritten character OCR Edition
:Text Detection & line cut.(Basic)
Introduction to Continuous Japanese handwritten character Image Recognition Python Programming.

 

 

日本語手書き文字画像・サンプルコードリンク:日本語の連続文字画像認識プログラム用

 

 

今回のプログラムでは、以下のサンプル画像

 

日本語手書き文字検出・OCR用サンプル画像


日本語手書き文字検出やOCR用のサンプル画像です。
画像には、「ひらがな」「カタカナ」「漢字」「ローマ字」「、点」「。丸」が含まれます。

 

内の

・「H2_tegaki.png」(線が入った横書きの日本語文字画像)

・「V2_tegaki.png」(線が入った縦書きの日本語文字画像)

の画像を使います。

 

すぐに使えるGoogle Colaboratoryサンプルコードリンク

Japanese-handwritten-text-detection-line-cut.ipynb | Google Colaboratory
(ファイル – ドライブにコピーを保存後にコピー環境で実行。プログラムを動かすにはGoogleアカウントでログインする必要があります)

 

 

【プログラムのライセンス】

 

 

The MIT License

Copyright 2021 child programmer

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 

 

【直線入りの水平・垂直の文章の日本語検出用:Pythonサンプルコード】

 

 

横書き・縦書き文章の日本語手書き文字検出(ブロック→行と列→個別文字)

このプログラムでの処理の流れ
直線の検出と除去→ブロック検出→行と列検出→個別文字検出

 

 

バージョン情報(Python・各種ライブラリ)

 

 

python 3.7.11
imutils 0.5.4
natsort 5.5.0
opencv(opencv-python)4.1.2
matplotlib 3.2.2

ローカル環境で、指定したバージョンのインストールが難しい場合、最新のバージョンを入れてみてください。

 

 

① Googleアカウントでログイン

 

 

Googleアカウントでログインした状態で、「ファイル – ドライブにメニューを保存」し、以下の手順を進めます。

 

 

② 直線の検出と除去をさせたい画像をアップロードします。

 

 

今回のプログラムで使用する画像:
・水平/垂直に近い文字列の画像(横書き・縦書き)
・線あり
・汚れ/ノイズなし

 

 

③ 直線の検出と除去のプログラムの実行。

 

 

以下の画像

「H2_tegaki.png」(横書きの日本語文字画像)
「V2_tegaki.png」(縦書きの日本語文字画像)

の直線の検出と除去を実行します。プログラムを実行前に「H2_tegaki.png」「V2_tegaki.png」をアップロードしておきます。
デフォルト(初期設定)では

・「H2_tegaki.png」(横書きの日本語文字画像:直線あり)の画像

直線の検出と除去をする設定にしてあります。

 

# 各種インポート
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt


# 直線検出する画像
input_file = 'H2_tegaki.png' # ここを変更
                             # 直掩検出と除去をしたい画像のファイル名を入力します

# 画像の読み込み
img = cv2.imread(input_file)

print('【直線を検出中・・・】直線検出する画像')
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show()

# モノクロ・グレースケール画像へ変換(2値化前の画像処理)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
retval, img_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
#plt.imshow(img_binary)
print('【直線を検出中・・・】2値化処理画像 - Binarization')
plt.imshow(cv2.cvtColor(img_binary, cv2.COLOR_BGR2RGB))
plt.show()

# 2値化画像で行う
# rho:画素単位で計算
# theta:ラジアン単位で計算
# threshold:直線とみなされるのに必要な最低限の点の数を意味するしきい値。
# 確率的ハフ変換:
# minLineLength:検出する直線の最小の長さを表します。この値より短い線分は検出されません
# maxLineGap:二つの点を一つの直線とみなす時に許容される最大の長さを表します
# この値より小さい二つの点は一つの直線とみなされます
# 必要に応じてパラメータの数値を変更してください
lines = cv2.HoughLinesP(img_binary, rho=1, theta=np.pi/360, threshold=15, minLineLength=55, maxLineGap=5.4)

if lines is None: # 直線が検出されない場合
  print('\n【直線の検出結果】')
  print(' 直線は検出されませんでした。')
  file_name = os.path.splitext(os.path.basename(input_file))[0]
  cv2.imwrite(f'line_cut_{file_name}.png', img)
else: # 直線が検出された場合
  print('\n【直線の検出結果】')
  print(' 直線が検出されました。検出した直線を削除します。')
  for line in lines:
      x1, y1, x2, y2 = line[0]
      # 検出した直線に赤線を引く
      red_lines_img = cv2.line(img, (x1,y1), (x2,y2), (0,0,255), 3)
  print('\n【直線検出部位の視覚化】')
  print(' 赤色部分が検出できた直線。')
  plt.imshow(cv2.cvtColor(red_lines_img, cv2.COLOR_BGR2RGB))
  plt.show()

  for line in lines:
      x1, y1, x2, y2 = line[0]
      # 検出した直線を消す(白で線を引く):2値化した際に黒で表示される
      no_lines_img = cv2.line(img, (x1,y1), (x2,y2), (255,255,255), 3)

      # 直線を除去した画像を元のファイル名の頭に「line_cut_」をつけて保存。「0」を指定でファイル名を取得
      file_name = os.path.splitext(os.path.basename(input_file))[0]
      cv2.imwrite(f'line_cut_{file_name}.png', no_lines_img)
  print('\n【直線検出部位の削除結果:元の画像から削除】')
  print(' 白色部分が検出した直線を消した場所(背景が白の場合は区別できません)。')
  plt.imshow(cv2.cvtColor(no_lines_img, cv2.COLOR_BGR2RGB))
  plt.show()

  line_cut_input_file = f'line_cut_{file_name}.png'
  img = cv2.imread(line_cut_input_file)
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

  # 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
  retval, img_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
  print('\n【直線検出部位の削除結果:2値化処理画像 - Binarization】')
  print(' 直線を除去した結果。')
  plt.imshow(cv2.cvtColor(img_binary, cv2.COLOR_BGR2RGB))
  plt.show()



# 直線検出した画像などの削除:
# 保存したい場合は、ここをコメントアウト(または削除)すると画像を保存できます。
file_name = os.path.splitext(os.path.basename(input_file))[0]
os.remove(f'line_cut_{file_name}.png')


print('\n\n* 直線を上手く検出できない場合は「cv2.HoughLinesP」のパラメータを変更してみてください。')

 

【出力結果例】(横書きの例)

 

【直線の検出結果】
直線が検出されました。検出した直線を削除します。

【直線検出部位の視覚化】
赤色部分が検出できた直線。

 

【直線検出部位の視覚化】直線検出部位の視覚化

 

【直線検出部位の削除結果:元の画像から削除】
白色部分が検出した直線を消した場所(背景が白の場合は区別できません)。

 

出力結果:【直線検出部位の削除結果:元の画像から削除】

 

 

④ 文字検出&直線検出と除去のプログラムの実行。

 

 

これまでに作成してきた文字検出プログラムに、直線検出と除去の機能を組み込んだプログラムです。
以下の画像

「H2_tegaki.png」(横書きの日本語文字画像)
「V2_tegaki.png」(縦書きの日本語文字画像)

の文字検出を実行します。プログラムを実行前に「H2_tegaki.png」「V2_tegaki.png」をアップロードしておきます。
デフォルト(初期設定)では

・「H2_tegaki.png」(横書きの日本語文字画像:直線あり)の画像

を検出する設定にしてあります。

 

#### 1.各種インポート #### 

import os
import cv2
import numpy as np
from imutils import contours
import matplotlib.pyplot as plt
import glob
from natsort import natsorted


#### 2.文字検出する画像 #### 

input_file = 'H2_tegaki.png' # ここを変更
                             # 文字領域検出したい画像のファイル名を入力します


#### 3.各種設定 ####

# 横書き・縦書きの設定
# 横書きの文字領域検出・縦書きの文字領域検出の選択
horizontal = 0
vertical = 1
OCR_mode = 0 # ここを変更
             # 横書きは「horizontal」または「0」・縦書きは「vertical」または「1」を入力


# 膨張処理の設定
#【横書き】大まかな文字領域の検出(ブロック検出)のための膨張処理(カーネルサイズ・膨張処理回数)の設定
block_horizontal_kernel_hight = 5  # カーネルの縦の高さ
block_horizontal_kernel_width = 5  # カーネルの横の幅
block_horizontal_iterations = 5    # 膨張処理回数

#【縦書き】大まかな文字領域の検出(ブロック検出)のための膨張処理(カーネルサイズ・膨張処理回数)の設定
block_vertical_kernel_hight = 5  # カーネルの縦の高さ
block_vertical_kernel_width = 5  # カーネルの横の幅
block_vertical_iterations = 9    # 膨張処理回数


#【横書き】行領域の検出(行検出)のための膨張処理(カーネルサイズ・膨張処理回数)の設定
column_horizontal_kernel_hight = 2  # カーネルの縦の高さ
column_horizontal_kernel_width = 5  # カーネルの横の幅
column_horizontal_iterations = 6    # 膨張処理回数

#【縦書き】列領域の検出(列検出)のための膨張処理(カーネルサイズ・膨張処理回数)の設定
row_vertical_kernel_hight = 5  # カーネルの縦の高さ
row_vertical_kernel_width = 3  # カーネルの横の幅
row_vertical_iterations = 6    # 膨張処理回数


#【横書き】個別の文字の検出(文字検出)のための膨張処理(カーネルサイズ・膨張処理回数)の設定
character_horizontal_kernel_hight = 5  # カーネルの縦の高さ 5
character_horizontal_kernel_width = 3  # カーネルの横の幅 3
character_horizontal_iterations = 2    # 膨張処理回数

#【縦書き】個別の文字の検出(文字検出)のための膨張処理(カーネルサイズ・膨張処理回数)の設定
character_vertical_kernel_hight = 3  # カーネルの縦の高さ 3
character_vertical_kernel_width = 5  # カーネルの横の幅 5
character_vertical_iterations = 2    # 膨張処理回数


# 輪郭のカット設定
# ブロック検出:文字領域検出した輪郭の「横幅」が、以下の範囲なら輪郭を残す
block_horizontal_height_minimum = 5  # 最小値(ピクセル)
block_horizontal_height_max = 1000   # 最大値(ピクセル)

# ブロック検出:文字領域検出した輪郭の「縦の高さ」が、以下の範囲なら輪郭を残す
block_vertical_height_minimum = 5  # 最小値(ピクセル)
block_vertical_height_max = 1000   # 最大値(ピクセル)


# 行検出:文字領域検出した輪郭の「横幅」が、以下の範囲なら輪郭を残す
row_column_horizontal_height_minimum = 5  # 最小値(ピクセル)
row_column_horizontal_height_max = 1000   # 最大値(ピクセル)

# 列検出:文字領域検出した輪郭の「縦の高さ」が、以下の範囲なら輪郭を残す
row_column_vertical_height_minimum = 5  # 最小値(ピクセル)
row_column_vertical_height_max = 1000   # 最大値(ピクセル)


# 個別の文字領域検出した輪郭の「横幅」が、以下の範囲なら輪郭を残す
character_text_detection_horizontal_height_minimum = 5  # 最小値(ピクセル)
character_text_detection_horizontal_height_max = 300    # 最大値(ピクセル)

# 個別の文字領域検出した輪郭の「縦の高さ」が、以下の範囲なら輪郭を残す
character_text_detection_vertical_height_minimum = 10  # 最小値(ピクセル)
character_text_detection_vertical_height_max = 300     # 最大値(ピクセル)




####  4.直線の検出と除去 ####
# 元の画像から直線を検出し、直線を除去します。
# 「line_cut_元の画像のファイル名.png」を作成します

def line_cut(OCR_input_file):

  # 画像の読み込み
  img = cv2.imread(OCR_input_file)

  print('【直線を検出中・・・】直線検出する画像')
  # 画像の表示
  plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
  plt.show()

  # モノクロ・グレースケール画像へ変換(2値化前の画像処理)
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

  # 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
  retval, img_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
  #plt.imshow(img_binary)
  print('【直線を検出中・・・】2値化処理画像 - Binarization')
  plt.imshow(cv2.cvtColor(img_binary, cv2.COLOR_BGR2RGB))
  plt.show()

  # 2値化画像で行う
  # rho:画素単位で計算
  # theta:ラジアン単位で計算
  # threshold:直線とみなされるのに必要な最低限の点の数を意味するしきい値。
  # 確率的ハフ変換:
  # minLineLength:検出する直線の最小の長さを表します。この値より短い線分は検出されません
  # maxLineGap:二つの点を一つの直線とみなす時に許容される最大の長さを表します
  # この値より小さい二つの点は一つの直線とみなされます
  # 必要に応じてパラメータの数値を変更してください
  lines = cv2.HoughLinesP(img_binary, rho=1, theta=np.pi/360, threshold=15, minLineLength=55, maxLineGap=5.4)

  if lines is None: # 直線が検出されない場合
    print('\n【直線の検出結果】')
    print(' 直線は検出されませんでした。')
    file_name = os.path.splitext(os.path.basename(input_file))[0]
    cv2.imwrite(f'line_cut_{file_name}.png', img)
  else: # 直線が検出された場合
    print('\n【直線の検出結果】')
    print(' 直線が検出されました。検出した直線を削除します。')
    for line in lines:
        x1, y1, x2, y2 = line[0]
        # 検出した直線に赤線を引く
        red_lines_img = cv2.line(img, (x1,y1), (x2,y2), (0,0,255), 3)
    print('\n【直線検出部位の視覚化】')
    print(' 赤色部分が検出できた直線。')
    # 画像の表示(直線を検出した画像)
    plt.imshow(cv2.cvtColor(red_lines_img, cv2.COLOR_BGR2RGB))
    plt.show()

    for line in lines:
        x1, y1, x2, y2 = line[0]
        # 検出した直線を消す(白で線を引く):2値化した際に黒で表示される
        no_lines_img = cv2.line(img, (x1,y1), (x2,y2), (255,255,255), 3)

        # 直線を除去した画像を元のファイル名の頭に「line_cut_」をつけて保存。「0」を指定でファイル名を取得
        file_name = os.path.splitext(os.path.basename(input_file))[0]
        cv2.imwrite(f'line_cut_{file_name}.png', no_lines_img)
    print('\n【直線検出部位の削除結果:元の画像から削除】')
    print(' 白色部分が検出した直線を消した場所(背景が白の場合は区別できません)。')
    # 画像の表示(直線を削除した画像)
    plt.imshow(cv2.cvtColor(no_lines_img, cv2.COLOR_BGR2RGB))
    plt.show()

    line_cut_input_file = f'line_cut_{file_name}.png'
    img = cv2.imread(line_cut_input_file)
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
    retval, img_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    print('\n【直線検出部位の削除結果:2値化処理画像 - Binarization】')
    print(' 直線を除去した結果。')
    # 画像の表示(直線を削除した2値画像)
    plt.imshow(cv2.cvtColor(img_binary, cv2.COLOR_BGR2RGB))
    plt.show()



## 直線の検出と除去の関数(line_cut)の実行
line_cut(input_file)







####  5.大まかな文字領域の検出(ブロック検出) ####
# 画像から、ブロック検出をおこないます
# 「block_ROI_img〜.png」(ブロック検出画像)を作成します

# 画像から文字領域を検出・抽出する処理
def block_contours (OCR_input_file):

  # 画像の読み込み
  img = cv2.imread(OCR_input_file)

  # モノクロ・グレースケール画像へ変換(2値化前の画像処理)
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

  # 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
  retval, img_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

  # 白部分の膨張処理(Dilation):モルフォロジー変換 - 2値画像を対象
  if OCR_mode == 0: # 横書きの場合
    kernel = np.ones((block_horizontal_kernel_hight, block_horizontal_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    img_dilation = cv2.dilate(img_binary,kernel,iterations = block_horizontal_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定
  elif OCR_mode == 1: # 縦書きの場合
    kernel = np.ones((block_vertical_kernel_hight, block_vertical_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    img_dilation = cv2.dilate(img_binary,kernel,iterations = block_vertical_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定

  # 解説用のコメント(2値化)
  print('\n【2値化処理画像 - Binarization】')
  print('  画像の2値化と白部分の膨張を工夫することで、大まかな文字領域の検出(ブロック検出)をしています。')
  print('  この段階で、文字が「白」として処理できていないと輪郭の検出がしにくいようでした。')

  # 膨張処理後の2値化画像の表示
  plt.imshow(cv2.cvtColor(img_dilation, cv2.COLOR_BGR2RGB))
  plt.show()


  # 輪郭の検出
  #「findContours」の返り値「cnts(contours)」は輪郭毎の座標組・「hierarchy」は輪郭の階層構造
  #「cv2.RETR_EXTERNAL」:最も外側の輪郭を返す
  #「cv2.CHAIN_APPROX_SIMPLE」:輪郭上の全点の情報を保持しない。輪郭の情報を圧縮
  cnts, hierarchy = cv2.findContours(img_dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

  if OCR_mode == 0: # 横書きの場合
    cnts, hierarchy = contours.sort_contours(cnts, method='top-to-bottom') # 上から下に並び替え
  elif OCR_mode == 1: # 縦書きの場合
    cnts, hierarchy = contours.sort_contours(cnts, method='right-to-left') # 右から左に並び替え

  # ROI(Region of Interest:興味領域・対象領域)抽出用の初期設定
  block_ROI_index = 0

  # 抽出した輪郭を「x, y, w(横の幅), h(縦の高さ)」の単純なリストに変換
  result = []
  for contour in cnts:  
    x, y, w, h = cv2.boundingRect(contour) # 外接矩形の左上の位置は(x,y),横の幅と縦の高さは(w,h)
      
    # 大きすぎる小さすぎる領域を除去。処理する画像サイズに合わせて微調整が必要
    if not block_vertical_height_minimum < w < block_vertical_height_max:
      continue
    if not block_horizontal_height_minimum < h < block_horizontal_height_max: #輪郭の描画は画像サイズを超えることもあるようでした。
      continue

    # ROI抽出:画像の切り抜きと保存 
    block_ROI = img[y:y+h, x:x+w]   
    cv2.imwrite('block_ROI_img{}.png'.format(block_ROI_index), block_ROI)
    block_ROI_index += 1
    #resultに要素を追加
    result.append([x, y, w, h])


  # 画面に矩形の輪郭を描画 (描画機能)
  for x, y, w, h in result:
      cv2.rectangle(img, (x, y), (x+w, y+h), (100, 255, 100), 3)  # 色の指定はRGB(100, 255, 100)。「3」は 太さ。数字を大きくすると太い輪郭が描画される。


  # 解説用のコメント(文字領域の輪郭検出・抽出)
  if OCR_mode == 0: # 横書きの場合
    print('\n【横書きの文字領域の輪郭検出・抽出結果 - Text detection・Contours】')
  elif OCR_mode == 1:  #縦書きの場合
    print('\n【縦書きの文字領域の輪郭検出・抽出結果 - Text detection・Contours】')
  print('  枠が大きすぎる場合・小さすぎる場合には輪郭を除去しています。画像によって微調整する必要があります。')

  # 文字領域の輪郭検出・抽出結果の表示
  plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
  plt.show()



# 元の画像ファイル名に「line_cut_」を追加
# 直線の検出と除去の関数(line_cut)を実行後に出力される画像をプログラムで使うための処理
file_name = os.path.splitext(os.path.basename(input_file))[0]
line_cut_input_file = f'line_cut_{file_name}.png'



## 大まかな文字領域の検出(ブロック検出)の関数(block_contours)の実行
block_contours (line_cut_input_file)






#### 6.横書き・縦書きの行・列領域の検出(行と列の検出) #### 
# 画像から行と列の検出をします
# 「row_column_ROI_img〜.png」(行や列を検出した画像)を作成します

def text_row_column_detection (OCR_input_file):
  # 画像の読み込み
  row_column_img = cv2.imread(OCR_input_file)
  
  # モノクロ・グレースケール画像へ変換(2値化前の画像処理)
  row_column_img_gray = cv2.cvtColor(row_column_img, cv2.COLOR_BGR2GRAY)

  # 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
  retval, row_column_img_binary = cv2.threshold(row_column_img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

  # 白部分の膨張処理(Dilation):モルフォロジー変換 - 2値画像を対象
  if OCR_mode == 0: # 横書きの場合
    kernel = np.ones((column_horizontal_kernel_hight, column_horizontal_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    row_column_img_dilation = cv2.dilate(row_column_img_binary,kernel,iterations = column_horizontal_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定  
    print('\n【各行の2値化処理画像 - Binarization】') 
  elif OCR_mode == 1:  #縦書きの場合
    kernel = np.ones((row_vertical_kernel_hight, row_vertical_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    row_column_img_dilation = cv2.dilate(row_column_img_binary,kernel,iterations = row_vertical_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定  
    print('\n【各列の2値化処理画像 - Binarization】')
  print('  画像の2値化と白部分の膨張を工夫することで、文字と文字の繋がりを検出しています。')
  print('  画像のテキストの文字の「太さ」「行間」「文字間隔」によっては、画像のリサイズの微調整や膨張処理の微調整が必要です。')

  # 膨張処理後の2値化画像の表示
  plt.imshow(cv2.cvtColor(row_column_img_dilation, cv2.COLOR_BGR2RGB))
  plt.show()

  # 輪郭の検出
  #「findContours」の返り値「contours」は輪郭毎の座標組・「hierarchy」は輪郭の階層構造
  #「cv2.RETR_EXTERNAL」:最も外側の輪郭を返す
  #「cv2.CHAIN_APPROX_SIMPLE」:輪郭上の全点の情報を保持しない。輪郭の情報を圧縮
  cnts, hierarchy = cv2.findContours(row_column_img_dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  if OCR_mode == 0: # 横書きの場合
    cnts, hierarchy = contours.sort_contours(cnts, method='top-to-bottom') # 上から下に並び替え
  elif OCR_mode == 1: # 縦書きの場合
    cnts, hierarchy = contours.sort_contours(cnts, method='right-to-left') # 右から左に並び替え

  # 抽出した輪郭を「x, y, w(横の幅), h(縦の高さ)」の単純なリストに変換
  row_column_result = []
  for row_column_contour in cnts:  
    x, y, w, h = cv2.boundingRect(row_column_contour) # 外接矩形の左上の位置は(x,y),横の幅と縦の高さは(w,h)
    
    # 大きすぎる小さすぎる領域を除去。処理する画像サイズに合わせて微調整が必要
    if not row_column_vertical_height_minimum < w < row_column_vertical_height_max:
      continue
    if not row_column_horizontal_height_minimum < h < row_column_horizontal_height_max: #輪郭の描画は画像サイズを超えることもあるようでした。
      continue

    # resultに要素を追加
    row_column_result.append([x, y, w, h])

  # 画面に矩形の輪郭を描画 (描画機能:for〜cv2.rectangleまでの2行をコメントアウト、または削除すると輪郭の描画を無効にできます)
  for x, y, w, h in row_column_result:
      cv2.rectangle(row_column_img, (x, y), (x+w, y+h), (100, 255, 100), 2)  # 色の指定はRGB(100, 255, 100)。「1」は 太さ。数字を大きくすると太い輪郭が描画される。

  # 解説用のコメント(輪郭検出・抽出)
  if OCR_mode == 0: # 横書きの場合
    print('\n【各行の文字の輪郭検出・抽出結果 - Text recognition・Contours】')
  elif OCR_mode == 1:  #縦書きの場合
    print('\n【各列の文字の輪郭検出・抽出結果 - Text recognition・Contours】')
  print('  枠が大きすぎる場合・小さすぎる場合には輪郭を除去しています。画像によって微調整する必要があります。')

  # 輪郭検出・抽出結果の表示
  plt.imshow(cv2.cvtColor(row_column_img, cv2.COLOR_BGR2RGB))
  plt.show()





##「row_column_ROI_img〜.png」の画像を保存するための処理


#「block_ROI_img〜.png」という名前のファイルの取得
file_list = glob.glob('block_ROI_img*png')

#「block_ROI_img〜.png」(〜の部分に0や1などの数字が入る)という名前のファイルを0から順番に並び替え
print(natsorted(file_list))

# ROI(Region of Interest:興味領域・対象領域)抽出用の初期設定
row_column_ROI_index = 0

#「block_ROI_img〜.png」のファイルを順番に処理する
for file in natsorted(file_list):
  row_column_img = cv2.imread(file)

  # モノクロ・グレースケール画像へ変換(2値化前の画像処理)
  row_column_img_gray = cv2.cvtColor(row_column_img, cv2.COLOR_BGR2GRAY)

  # 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
  retval, row_column_img_binary = cv2.threshold(row_column_img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

  # 白部分の膨張処理(Dilation):モルフォロジー変換 - 2値画像を対象
  if OCR_mode == 0: # 横書きの場合
    kernel = np.ones((column_horizontal_kernel_hight, column_horizontal_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    row_column_img_dilation = cv2.dilate(row_column_img_binary,kernel,iterations = column_horizontal_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定  

  elif OCR_mode == 1:  #縦書きの場合
    kernel = np.ones((row_vertical_kernel_hight, row_vertical_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    row_column_img_dilation = cv2.dilate(row_column_img_binary,kernel,iterations = row_vertical_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定  

  cnts, hierarchy = cv2.findContours(row_column_img_dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  if OCR_mode == 0: # 横書きの場合
      cnts, hierarchy = contours.sort_contours(cnts, method='top-to-bottom') # 上から下に並び替え
  elif OCR_mode == 1: # 縦書きの場合
      cnts, hierarchy = contours.sort_contours(cnts, method='right-to-left') # 右から左に並び替え

  # 抽出した輪郭を「x, y, w(横の幅), h(縦の高さ)」の単純なリストに変換
  for row_column_contour in cnts:  
    x, y, w, h = cv2.boundingRect(row_column_contour) # 外接矩形の左上の位置は(x,y),横の幅と縦の高さは(w,h)

    # ROI抽出:画像の切り抜きと保存。 ここで最後の処理だけの行と列の画像ファイルが残るのみなのでできない
    row_column_ROI = row_column_img[y:y+h, x:x+w]   
    cv2.imwrite('row_column_ROI_img{}.png'.format(row_column_ROI_index), row_column_ROI)

    row_column_ROI_index += 1






#### 7.個別の文字の検出(文字検出)+ 個別の文字検出枠の描画設定 #### 
# 「row_column_ROI_img〜.png」(行や列を検出した画像)から、行や列ごとに個別の文字を検出します
# 「OCR_img〜.png」(個別文字検出画像)を作成します
# 「個別の文字検出枠の描画設定:画面に矩形の輪郭を描画」のコードをコメントアウトまたは削除すると個別の文字検出枠を無効化できます
# 個別の文字検出枠の有無や太さによってOCR結果の精度に影響がでます。基本的に枠線が無い方が精度は良いです
# 個別の文字検出枠があると、どの程度文字検出ができているか把握しやすいので、お好みに応じて調整してみてください


def find_draw_contours (OCR_input_file):
  # 画像の読み込み
  predict_img = cv2.imread(OCR_input_file)
  
  # モノクロ・グレースケール画像へ変換(2値化前の画像処理)
  predict_img_gray = cv2.cvtColor(predict_img, cv2.COLOR_BGR2GRAY)

  # 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
  retval, predict_img_binary = cv2.threshold(predict_img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

  # 白部分の膨張処理(Dilation):モルフォロジー変換 - 2値画像を対象
  if OCR_mode == 0: # 横書きの場合
    kernel = np.ones((character_horizontal_kernel_hight, character_horizontal_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    predict_img_dilation = cv2.dilate(predict_img_binary,kernel,iterations = character_horizontal_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定
  elif OCR_mode == 1: #縦書きの場合
    kernel = np.ones((character_vertical_kernel_hight, character_vertical_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    predict_img_dilation = cv2.dilate(predict_img_binary,kernel,iterations = character_vertical_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定

  # 解説用のコメント(2値化)
  if OCR_mode == 0: # 横書きの場合
    print('\n【各行の2値化処理画像 - Binarization】')
  elif OCR_mode == 1:  #縦書きの場合
    print('\n【各列の2値化処理画像 - Binarization】')
  print('  画像の2値化と白部分の膨張を工夫することで、個別の文字を検出しやすいようにしています。')
  print('  画像のテキストの文字の「太さ」「行間」「文字間隔」によっては、画像のリサイズや膨張処理の微調整が必要です。')
  print('  この段階で、文字が他の文字と繋がってしまうと、個別の文字の検出ができなくなります。')

  # 膨張処理後の2値化画像の表示
  plt.imshow(cv2.cvtColor(predict_img_dilation, cv2.COLOR_BGR2RGB))
  plt.show()



  # 輪郭の検出
  #「findContours」の返り値「contours」は輪郭毎の座標組・「hierarchy」は輪郭の階層構造
  #「cv2.RETR_EXTERNAL」:最も外側の輪郭を返す
  #「cv2.CHAIN_APPROX_SIMPLE」:輪郭上の全点の情報を保持しない。輪郭の情報を圧縮
  cnts, hierarchy = cv2.findContours(predict_img_dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  if OCR_mode == 0: # 横書きの場合
      cnts, hierarchy = contours.sort_contours(cnts, method='left-to-right') # 左から右に並び替え
  elif OCR_mode == 1: # 縦書きの場合
      cnts, hierarchy = contours.sort_contours(cnts, method='top-to-bottom') # 上から下に並び替え

  # ROI(Region of Interest:興味領域・対象領域)抽出用の初期設定
  OCR_index = 0

  # 抽出した輪郭を「x, y, w(横の幅), h(縦の高さ)」の単純なリストに変換
  predict_result = []
  for predict_contour in cnts:  
    x, y, w, h = cv2.boundingRect(predict_contour) # 外接矩形の左上の位置は(x,y),横の幅と縦の高さは(w,h)
    
    # 大きすぎる小さすぎる領域を除去。処理する画像サイズに合わせて微調整が必要
    if not character_text_detection_horizontal_height_minimum < w < character_text_detection_horizontal_height_max:
      continue
    if not character_text_detection_vertical_height_minimum < h < character_text_detection_vertical_height_max: #輪郭の描画は画像サイズを超えることもあるようでした。
      continue

    # ROI抽出:画像の切り抜きと保存。
    predict_ROI = predict_img[y:y+h, x:x+w]   
    cv2.imwrite('OCR_img{}.png'.format(OCR_index), predict_ROI)
    OCR_index += 1

    #resultに要素を追加
    predict_result.append([x, y, w, h])

  # 画面に矩形の輪郭を描画 (描画機能:for〜cv2.rectangleまでの2行をコメントアウト、または削除すると輪郭の描画を無効にできます)
  for x, y, w, h in predict_result:
      cv2.rectangle(predict_img, (x, y), (x+w, y+h), (100, 255, 100), 2)  # 色の指定はRGB(100, 255, 100)。「2」は 太さ。数字を大きくすると太い輪郭が描画される。


  # 解説用のコメント(輪郭検出・抽出)
  if OCR_mode == 0: # 横書きの場合
    print('\n【各行の文字の輪郭検出・抽出結果 - Text recognition・Contours】')
  elif OCR_mode == 1:  #縦書きの場合
    print('\n【各列の文字の輪郭検出・抽出結果 - Text recognition・Contours】')
  print('  枠が大きすぎる場合・小さすぎる場合には輪郭を除去しています。画像によって微調整する必要があります。')

  # 輪郭検出・抽出結果の表示
  plt.imshow(cv2.cvtColor(predict_img, cv2.COLOR_BGR2RGB))
  plt.show()




##「OCR_img〜.png」の画像を保存するための処理


#「row_column_ROI_img〜.png」という名前のファイルの取得
file_list = glob.glob('row_column_ROI_img*png')

#「row_column_ROI_img〜.png」(〜の部分に0や1などの数字が入る)という名前のファイルを0から順番に並び替え
print(natsorted(file_list))

# ROI(Region of Interest:興味領域・対象領域)抽出用の初期設定
OCR_index = 0

#「row_column_ROI_img〜.png」のファイルを順番に処理する
for file in natsorted(file_list):
  predict_img = cv2.imread(file)

  # モノクロ・グレースケール画像へ変換(2値化前の画像処理)
  predict_img_gray = cv2.cvtColor(predict_img, cv2.COLOR_BGR2GRAY)

  # 2値化(Binarization):白(1)黒(0)のシンプルな2値画像に変換
  retval, predict_img_binary = cv2.threshold(predict_img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

  # 白部分の膨張処理(Dilation):モルフォロジー変換 - 2値画像を対象
  if OCR_mode == 0: # 横書きの場合
    kernel = np.ones((character_horizontal_kernel_hight, character_horizontal_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    predict_img_dilation = cv2.dilate(predict_img_binary,kernel,iterations = character_horizontal_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定
  elif OCR_mode == 1: #縦書きの場合
    kernel = np.ones((character_vertical_kernel_hight, character_vertical_kernel_width),np.uint8) # カーネル(構造的要素):全要素の値が1の縦横が任意のピクセルのカーネル
    predict_img_dilation = cv2.dilate(predict_img_binary,kernel,iterations = character_vertical_iterations) #「iterations=」繰り返し膨張処理を行う回数を指定

  cnts, hierarchy = cv2.findContours(predict_img_dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  if OCR_mode == 0: # 横書きの場合
      cnts, hierarchy = contours.sort_contours(cnts, method='left-to-right') # 左から右に並び替え
  elif OCR_mode == 1: # 縦書きの場合
      cnts, hierarchy = contours.sort_contours(cnts, method='top-to-bottom') # 上から下に並び替え

  # 抽出した輪郭を「x, y, w(横の幅), h(縦の高さ)」の単純なリストに変換
  predict_result = []
  for predict_contour in cnts:  
    x, y, w, h = cv2.boundingRect(predict_contour) # 外接矩形の左上の位置は(x,y),横の幅と縦の高さは(w,h)
    
    # 大きすぎる小さすぎる領域を除去。処理する画像サイズに合わせて微調整が必要
    if not character_text_detection_horizontal_height_minimum < w < character_text_detection_horizontal_height_max:
      continue
    if not character_text_detection_vertical_height_minimum < h < character_text_detection_vertical_height_max: #輪郭の描画は画像サイズを超えることもあるようでした。
      continue

    # ROI抽出:画像の切り抜きと保存。→ 再学習用に別プログラムを作成する OCR_index自体は、文字数判断のために必要かもしれない。
    predict_ROI = predict_img[y:y+h, x:x+w]   
    cv2.imwrite('OCR_img{}.png'.format(OCR_index), predict_ROI)
    OCR_index += 1





#### 8.関数の実行等 #### 
 

# 横書き・縦書きの行・列領域の検出(行と列の検出)する関数(text_row_column_detection)の実行
#「block_ROI_img〜.png」という名前のファイルの取得
file_list = glob.glob('block_ROI_img*png')

#「block_ROI_img〜.png」(〜の部分に0や1などの数字が入る)という名前のファイルを0から順番に並び替え
print(natsorted(file_list))

#「block_ROI_img〜.png」のファイルを順番に、
# 横書き・縦書きの行・列領域の検出(行と列の検出)する関数(text_row_column_detection)に入れる
for file in natsorted(file_list):
  text_row_column_detection(file) 


# 個別の文字の検出(文字検出)する関数(find_draw_contours)の実行
#「row_column_ROI_img〜.png」という名前のファイルの取得
file_list = glob.glob('row_column_ROI_img*png')

#「row_column_ROI_img〜.png」(〜の部分に0や1などの数字が入る)という名前のファイルを0から順番に並び替え
print(natsorted(file_list))

#「row_column_ROI_img〜.png」のファイルを順番に、
# 個別の文字の検出(文字検出)する関数(find_draw_contours)に入れる
for file in natsorted(file_list):
  find_draw_contours(file) 



print('\n\n* 上手く検出できない場合は「3.各種設定」の所で、「膨張処理の設定」「輪郭のカット設定」などを変更してみてください。')





#### 9.出力した画像ファイルの削除 #### 
# 今回のコードでは、画像を一度作成後に、プログラム内で利用し、最後に作成した画像を削除しています
# そのため、次に文字検出を実行する画像内の文字検出数などが少ないと、誤作動が起きるリスクがあります
# 画像ファイルを保存した場合には、一度生成された画像を削除してから、新たな画像の文字検出を試みてください
# 「個別の文字領域検出した画像(OCR_img*png)の削除」のコードをコメントアウトまたは、削除すると個別の文字検出画像を保存できます

# 直線検出した画像(line_cut_元のファイル名.png)の削除:
# 保存したい場合は、ここをコメントアウト(または削除)すると画像を保存できます
file_name = os.path.splitext(os.path.basename(input_file))[0]
os.remove(f'line_cut_{file_name}.png')


# 大まかな文字領域の検出(ブロック検出)した画像(「block_ROI_img〜.png」)の削除:
# 保存したい場合は、ここをコメントアウト(または削除)すると画像を保存できます
file_list = glob.glob("block_ROI_img*png")
for file in natsorted(file_list):
  os.remove(file)


# 横書き・縦書きの行・列領域の検出(行と列の検出)した画像(「row_column_ROI_img〜.png」)の削除:
# 保存したい場合は、ここをコメントアウト(または削除)すると画像を保存できます
file_list = glob.glob("row_column_ROI_img*png")
for file in natsorted(file_list):
  os.remove(file)


# 個別の文字の検出(文字検出)画像(「OCR_img〜.png」)の削除:
# 保存したい場合は、ここをコメントアウト(または削除)すると画像を保存できます(今回は一部の画像のみ。ステップ4のプログラムでは全てに対応。)
file_list = glob.glob('OCR_img*png')
for file in natsorted(file_list):
  os.remove(file)

 

【出力結果例】(横書きの例)

出力結果例:【日本語 - 手書き編】連続文字検出:直線除去の機能を実装 - 横書き・縦書き(ひらがな・カタカナ・漢字・ローマ字・、点・。丸)

 

【直線の検出結果】
直線が検出されました。検出した直線を削除します。

【直線検出部位の視覚化】
赤色部分が検出できた直線。

 

【直線検出部位の視覚化】直線検出部位の視覚化

 

【直線検出部位の削除結果:元の画像から削除】
白色部分が検出した直線を消した場所(背景が白の場合は区別できません)。

 

出力結果:【直線検出部位の削除結果:元の画像から削除】

 

【横書きの文字領域の輪郭検出・抽出結果 - Text detection・Contours】
枠が大きすぎる場合・小さすぎる場合には輪郭を除去しています。画像によって微調整する必要があります。
【横書きの文字領域の輪郭検出・抽出結果 - Text detection・Contours】シンプルな横書き・縦書き文章の日本語手書き文字検出

 

【各行の文字の輪郭検出・抽出結果 横書き - Text recognition・Contours】シンプルな横書き・縦書き文章の日本語手書き文字検出

 

【各行の文字の輪郭検出・抽出結果 - Text recognition・Contours】シンプルな横書き・縦書き文章の日本語手書き文字検出

 

【各行の文字の輪郭検出・抽出結果 - Text recognition・Contours】
枠が大きすぎる場合・小さすぎる場合には輪郭を除去しています。画像によって微調整する必要があります。
【各行の文字の輪郭検出・抽出結果 1 - Text recognition・Contours】シンプルな横書き・縦書き文章の日本語手書き文字検出

 

【各行の文字の輪郭検出・抽出結果 2 - Text recognition・Contours】シンプルな横書き・縦書き文章の日本語手書き文字検出

 

【各行の文字の輪郭検出・抽出結果 3 - Text recognition・Contours】シンプルな横書き・縦書き文章の日本語手書き文字検出

 

【各行の文字の輪郭検出・抽出結果 4 - Text recognition・Contours】シンプルな横書き・縦書き文章の日本語手書き文字検出

 

【各行の文字の輪郭検出・抽出結果 5 - Text recognition・Contours】シンプルな横書き・縦書き文章の日本語手書き文字検出

 

【次のステップ】

続いて、点々などのノイズの入った画像の文字検出に挑戦

【ステップ3-3】
点々などのノイズの入った画像の日本語検出
ノイズ除去の機能を実装:横書き・縦書き文章の日本語手書き文字検出(ブロック→行と列→個別文字)

 

 

by 子供プログラマー | プログラミング入門ウェブ教室

 

【日本語手書きOCR編】連続文字画像認識プログラミング入門(Python・OpenCV・Keras・CNN)| 一覧ページ