ノンブルを削除したいのであるーOpenCV編

 あけましておめでとうございます。ってもう2月ですね、プッチ神父は正しかった。というわけで2020の目標がきまりました

「ノンブルを削除する」

  1. 理由
     スマホ片手に自炊した本を読みながら寝ると簡単に寝られるんですよ。特にうさんくさい歴史系の文庫本。
    「明智光秀は天海、その15代後が小五郎で名探偵・・キリンがキリンが・・・・zzzzz」
     って感じ。なので日々暇があればブックオフで買った100円文庫本をせっせとPDF化しています。自炊本を読むならこのアプリ”Perfect Viewer”、ほぼ完璧なアプリ(本棚を階層化できれば・・・)です。しかし、スマホの画面は小さい。拡大して読むのですが、ノンブル(ページ番号や章の名前など上下についているあれ)が邪魔なのです。
    image
    普段は目に優しい白黒反転で見ています。これも別問題を引き起こすのですが別途挑戦したいと

     後述のソフトでもノンブル削除の失敗は稀に発生するので、鉄オタ選手権近鉄の中津行を嘆いていたメーテルと重なるんです。

  2. 世間では
     
    ググればすぐ出てくると思いますが、世間では有志の方々が作成した自炊用ソフトが沢山あります、毎度お世話になっております。その中でノンブル削除を簡単にできるソフトとして次の2つがあります、というか2つしか知りません。

    1)ChainLP系
     言わずと知れたNo.722さんの有名ソフトです。もう7年前なんですね、OpenCVなしでどーやって作ったんだ?今頃立派な学者さんor研究者さんになっているのでしょうか?ただ、ノンブルの位置とかを指定しないと機能しないことが多いんです。

    imageimage
    eTilTranでもノンブル位置を指定しなければなりませぬ。
    image

    2)かんたんPDFDiet
     Smart-PDA.net 管理者のかんたんPDFDiet。希望通り、全自動でノンブルを削除してくれる機能があります。
    image
    だがしかし、この辺りのノイズに弱いんです。ちゃんと前処理しない方が私が悪いのですが、消費者はわがままですw。
    image

    両ソフトともC#、さらにPDFDietの方はOpenCVsharpを使っています。なのでILSpyを使うと良好なソースを取得できるのですが、解析するとなるとなにかと面倒であります。

  3. そして、月日は流れ・・・
     「ラグビーW杯、おもすれー。南ア、前から後ろまでキャラ揃い過ぎだわ、ブタゴリラさいこー」と自堕落な日々を過ごしてきたのですが、気になる情報が入ってきます。
    1)へっぽこ知識で作る画像処理プログラム 「自炊本のページ抜けチェック」 3日目
    2)【Python/OpenCV】射影(投影)分布を使って横一列の文字列から文字領域を検出

     「これ、ノンブルを削除するソフトを作れそうじゃね?」となり、深みにはまって今に至るのであります。

  4. ノンブル削除プログラム
     3.で示したリンクの知識を使い。ノンブル削除をしてみました。射影を取得するには3-1)の積分を使い、プログラムの骨格は3-2)を使っています。jpg画像で自炊したPDFの上の方の矩形と下の方矩形で浮いている矩形を白塗りしてjpg出力する作りとなっています。
    image
    eTilTranみたいな画面ができたよ!

     青ラインから上を削除する感じのプログラムです。細かいことはかなり怪しいですが、「こまけーことはいいんだよ」の精神です。今をときめくpythonであります。パイソン、訳あって勉強中であります。追記:積分の0とかで矩形が微妙にずれています。いろいろ修正中。コンセプトだけ感じてくださいw境界テストって大事だわ

import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

import PyPDF2
import io,glob,sys

def Projection_seki(img, height, width,axis=2,save=0):
	#積分をして水平垂直方向の射影を返す

	integral = cv2.integral(img[0:height,0:width])
	
	array_V = None
	array_H = None
	
	#縦方向の積分(H方向の配列)
	if(axis==0 or axis==2):
		#heightの積分値はarray[height]に入る
		array_V2 = integral[height,:]
		array_V2=np.insert(array_V2, 0,0) 	
		#最後の値は不要
		array_V = np.delete(np.diff(array_V2,1),-1)
		
	#横方向の積分(V方向の配列)
	if(axis==1 or axis==2):
		array_H2 = integral[:,width]		
		array_H2=np.insert(array_H2,0,0)
		#最後の値は不要
		array_H = np.delete(np.diff(array_H2,1),-1)
	
	#射影値画像を保存するか?
	if (array_V is not None and save==1):
		fig = plt.figure()
		ax = fig.add_subplot(1, 1, 1)
		x_axis = np.arange(width)
		ax.bar(x_axis, array_V)
		fig.savefig("hist_V.png")
	
	if (array_H is not None and save==1):
		fig = plt.figure()
		ax = fig.add_subplot(1, 1, 1)
		x_axis = np.arange(height)
		ax.barh(x_axis, array_H)
		fig.savefig("hist_H.png")

	return array_V,array_H

def Detect_Position(RECT_DISTANCE, width, array_V):
	#射影値の配列から白(!=0)の範囲をリストにして返す

	char_List = []
	flg = False
	posi1 = 0
	posi2 = 0
	beforepos=-RECT_DISTANCE

	for i in range(width):
		val = array_V[i]
		if (flg==False and val > 0):
			#白の開始点を発見
			if(i - beforepos > RECT_DISTANCE):
				#前の終了点との間隔が十分
				flg = True
				posi1 = i
			else :
				#前の終了点との間隔が不十分なのて
				#前の終了点を削除して終了点検索を継続
				flg = True
				if(len(char_List) > 0):
					posi1,x = char_List[-1]
					char_List.pop()

		if (flg == True and val == 0):
			#白の終了点を発見
			flg = False
			posi2 = i
			char_List.append((posi1,posi2))
			beforepos=posi2
			
	if flg==True :
		#最後まで白の時
		char_List.append((posi1,width-1))
			
	return char_List


def Rect_Combine(rect_List):
	#矩形の結合	
	arr = np.array(rect_List)
	xymin=arr.min(axis=0)
	xymax=arr.max(axis=0) 

	return xymin[0],xymin[1],xymax[2],xymax[3]

def Detect_Img(img,pageno=0):	
	#opencvの画像から矩形を検出して描画
	height = img.shape[0]
	width = img.shape[1]
		
	# convert gray scale image
	gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
	
	# 2値化
	ret, bw_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_OTSU)
	height, width = bw_img.shape
	
	# 反転
	integral = cv2.bitwise_not(bw_img)
	
	# 両方向の射影値を取得
	array_V ,array_H = Projection_seki(integral, height, width,2,0)
	
	#縦をチェックして文字の範囲をリストで返す。xxドットまでは合体させる
	H_DISTANCE = 15 #縦の許容間隔
	char_list= []
	char_List = Detect_Position(H_DISTANCE, height, array_H)
	
	#横をチェックして文字の範囲をリストで返す。xxドットまでは合体させる
	W_DISTANCE = 60 #横の許容間隔
	#矩形の上限、下限
	UPPER_LINE = height*0.12 #これより上の矩形は削除
	LOWER_LINE = height-UPPER_LINE#これより下の矩形は削除

	for ystart,yend in char_List:
		#削除範囲のないならcontinue
		if (yend > UPPER_LINE and ystart < LOWER_LINE):
			continue
		
		char_List2 = []
	
		#文字のある範囲で横方向切れ目をチェック
		array_V ,array_H = Projection_seki(integral[ystart:yend,:], yend-ystart, width,0,0)
		char_List2 = Detect_Position(W_DISTANCE, width, array_V)
		
		for xstart,xend in char_List2:
			x1,y1,x2,y2=xstart,ystart,xend,yend 
			print ("検出矩形:",x1,y1,x2,y2)
			#矩形を白で塗りつぶし
			img = cv2.rectangle(img, (x1,y1),(x2,y2) , (255,255,255), -1)
			
			
	return img

 

if __name__ == "__main__":
	
	#保存ディレクトリ
	if (len(sys.argv) == 2):
		pdffile = sys.argv[1]
	else :
		pdffile="./99.pdf"
	#保存ディレクトリ
	save_dir = 'b:/book/'

	in_pdf = PyPDF2.PdfFileReader(pdffile)
	
	for page_no in range(in_pdf.getNumPages()):
		page = in_pdf.getPage(page_no)
		if page_no <0 : continue
		if page_no >638 : break
		
		print ("page {}".format(page_no))
		# Images are part of a page's `/Resources/XObject`
		r = page['/Resources']
		if '/XObject' not in r:
			continue
		for k, v in r['/XObject'].items():
			vobj = v.getObject()
			# We are only interested in images...
			if vobj['/Subtype'] != '/Image' or '/Filter' not in vobj:
				continue
			if vobj['/Filter'] == ['/DCTDecode'] or  vobj['/Filter'] == '/DCTDecode' :
				# A compressed image
				img = Image.open(io.BytesIO(vobj._data))
				img_bin = io.BytesIO(vobj._data) #メモリに保持してディレクトリ偽装みたいなことする
				#PILイメージ <- バイナリーストリーム
				img_pil = Image.open(img_bin)
				#numpy配列(RGBA) <- PILイメージ
				img_numpy = np.asarray(img_pil)
				#numpy配列(BGR) <- numpy配列(RGBA)
				img = cv2.cvtColor(img_numpy, cv2.COLOR_RGBA2BGR)

				#画像解析
				new_img = Detect_Img(img,page_no)

				filename = save_dir + "detect{:03}.jpg".format(page_no)
				cv2.imwrite(filename,new_img)

	

そして、話はもうちょっとだけ続くのじゃ
 苦労して作ったのですが、パラメータをPDF毎に設定しなきゃいけない=ちょーめんどくさい。「データが沢山あればアルゴリズムなんていらない!」という昨今。エロ画像と自炊画像はたくさん持っているので細かい数値はCPUが考えてもいいんじゃないか?そして

「まさかの機械学習編に続く」と思いたい

#先につながる話題はいい話題、たのしー。それにしても南ア、終わってみれば過去最高のスター集団だったな、ブタゴリラ、デラクーク、コルビ、ポラードすげーわ

カテゴリー: 20世紀プログラマ パーマリンク

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中