スキャンした写真を一緒に分離する(Python 3 + OpenCV3)

何十もの家族の写真アルバムがキャビネットとほこりだらけの棚の引き出しに何十年も保管されてきました。 それらのいくつかの状態は、長い間、蓄積された素材の「デジタル化」について考えさせてきました。 今後のプロセスを少しでも高速化するために、一度に複数の写真をスキャンすることにしました。 しかし、結果のコンテンツを集め、それを手で分割して別のフレームに分割する見込みはありませんでした。 最後に、解決策が生まれました...



pythonの基礎とコンピュータービジョンへの関心を知り尽くしたことを考えると、明らかになった実用的なタスクが重宝しました。 最初は、Pixelmatorで他の3人から収集した画像をテストしました。 今後は、元の画像で写真が傾く可能性を予見していなかったと言わなければなりません。 それぞれ独自の方向に。 次に、いくつかの写真を選択し、MFPを数回訪問して、2つの画像を取得し、その上でコードのパフォーマンスを確認しました。 アルバムの個人的なコンテンツのため、他の画像の例を示します。



そのため、プログラムは端末から見えます。







ソース画像




結果
















に興味がありますか? 続けます。



OpenCVのインストールは検討していません。Webには多数の手順が記載されています。 したがって、最初に依存関係をインポートします。



from numpy import int0, zeros_like, deg2rad, sin, cos, dot, array as nparray from math import ceil import cv2 from os import mkdir, chdir from os.path import basename, dirname, isdir, join as path_join from argparse import ArgumentParser
      
      





主な()関数は次のとおりです。



 def main(): parser = ArgumentParser(description='   ') parser.add_argument('-n', type=int, dest='number', required=True, help='   ') parser.add_argument('-i', dest='image', required=True, help='   ') args = parser.parse_args() folder = dirname(args.image) image_name = basename(args.image) extension = image_name.split('.')[-1] image_name_without_extension = '.'.join(image_name.split('.')[:-1]) if folder: chdir(folder) if not isdir(image_name_without_extension): mkdir(image_name_without_extension) image = cv2.imread(image_name) contours = get_contours(image, args.number) i = 1 for c in contours: ca = int0(cv2.boxPoints(cv2.minAreaRect(c))) im = image[ca[2][1]:ca[0][1],ca[1][0]:ca[3][0]] im = rotate(ca, im) cv2.imwrite(path_join(image_name_without_extension, '%s.%s'%(i, extension)), im) i += 1 cv2.destroyAllWindows()
      
      





複雑すぎる? 順番に行きましょう。



 parser = ArgumentParser(description='   ') parser.add_argument('-n', type=int, dest='number', required=True, help='   ') parser.add_argument('-i', dest='image', required=True, help='   ') args = parser.parse_args()
      
      





将来のアプリケーションを説明する引数の説明を使用して、ArgumentParser型のオブジェクトを作成します。 整数型の引数 '-n'を追加します。これは 'number'として保存し、コマンド-h /-helpのパラメーターの説明も添付します。 同様に、文字列引数 '-i'では、両方の引数が必要です(必須= True)。 最後の行では、起動時にスクリプトに渡された引数を分析します。



次:



 folder = dirname(args.image) image_name = basename(args.image) extension = image_name.split('.')[-1] image_name_without_extension = ''.join(image_name.split('.')[:-1])
      
      





args.imageパラメーターから、ファイル(存在する場合)、ファイル名、拡張子、および拡張子なしの名前を含むディレクトリへのパスを取得します。



 if folder: chdir(folder) if not isdir(image_name_without_extension): mkdir(image_name_without_extension)
      
      





イメージのあるフォルダーが現在の作業ディレクトリでない場合は、そこに移動します。 代わりに、拡張子なしのソースイメージの名前でフォルダーを作成し、最終的なフォルダーを追加します。



 image = cv2.imread(image_name) contours = get_contours(image, args.number)
      
      





画像を開きます。 画像の数であるargs.numberのそれぞれの記述長方形を見つけます。

get_contours()関数については以下で説明します。



カウンターを設定します。 次に、見つかった画像の輪郭を反復処理するサイクルが開始されます。



 i=1 for c in contours:
      
      





ループ内:



長方形を記述する最小の点の2次元配列を取得します。



 ca = int0(cv2.boxPoints(cv2.minAreaRect(c)))
      
      





これらのポイントで画像をトリミングします。



 im = image[ca[2][1]:ca[0][1],ca[1][0]:ca[3][0]]
      
      





画像を回転させます。 rotate()関数の説明は以下のとおりです。



 im = rotate(ca, im)
      
      





拡張子なしのソースファイルの名前で作成されたフォルダ内の対応する番号の下に画像を保存します。



 cv2.imwrite(path_join(image_name_without_extension, '%s.%s'%(i, extension)), im)
      
      





カウンターが刻々と過ぎています。



 i += 1
      
      





そして、元の画像のすべての要素を確認するまでです。 一般に、それですべてです。 次に、機能を検討します。



get_contour()は次のようになります。



 def get_contours(src, num=0): src = cv2.copyMakeBorder(src, 2, 2, 2, 2, cv2.BORDER_CONSTANT, value=(255, 255, 255)) gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) thresh = cv2.threshold(gray, 230, 255, cv2.THRESH_BINARY)[1] contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_TC89_L1)[1] if not num: return sorted(contours, key = cv2.contourArea, reverse = True)[1] else: return sorted(contours, key = cv2.contourArea, reverse = True)[1:num+1]
      
      





関数内で、画像を少し拡大して、エッジに「くっついている」画像を特定し、グレーの陰影に減らし、二値化して輪郭を見つけます。 この関数の呼び出しがrotate()関数から行われた場合、numパラメーターは0のままで、1つだけを返します(2番目は、インデックス0の最初の要素がソースイメージ全体を記述するため)。輪郭の領域でソートされた配列の要素(輪郭) 。 呼び出しがmain()関数から行われた場合、numパラメーターにはargs.numberが含まれ、get_contours()関数はargs.number of outlinesを返します。



()関数を回転させます。 ここでは、コード内のコメントでうまくいくようにします。



 def rotate(contour, src): #   angle = cv2.minAreaRect(contour)[2] if angle > 45: angle -= 90 if angle < -45: angle += 90 #    w, h = src.shape[1], src.shape[0] #    rotangle = deg2rad(angle) #      nw = abs(sin(rotangle)*h) + abs(cos(rotangle)*w) nh = abs(cos(rotangle)*h) + abs(sin(rotangle)*w) #   rotation_matrix = cv2.getRotationMatrix2D((nw*0.5, nh*0.5), angle, 1.0) rotatiom_move = dot(rotation_matrix, nparray([(nw-w)*0.5, (nh-h)*0.5,0])) rotation_matrix[0,2] += rotatiom_move[0] rotation_matrix[1,2] += rotatiom_move[1] #  src = cv2.warpAffine(src, rotation_matrix, (int(ceil(nw)), int(ceil(nh))), flags=cv2.INTER_LANCZOS4, borderValue=(255,255,255)) #      ca = int0(cv2.boxPoints(cv2.minAreaRect(get_contours(src)))) #     , ""  return src[ca[2][1]+14:ca[0][1]-3,ca[1][0]+3:ca[3][0]-3]
      
      





読んでくれてありがとう。 誰かがアルゴリズムを最適化する方法と私が作った妨害の種類を知っているなら、コメントへようこそ。 誰かがこの記事をお役に立てば幸いです。



コード全体
 #!/usr/local/bin/python3 from numpy import int0, zeros_like, deg2rad, sin, cos, dot, array as nparray from math import ceil import cv2 from os import mkdir, chdir from os.path import basename, dirname, isdir, join as path_join from argparse import ArgumentParser def get_contours(src, num=0): #     ""   src = cv2.copyMakeBorder(src, 2, 2, 2, 2, cv2.BORDER_CONSTANT, value=(255, 255, 255)) #    gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) # thresh = cv2.threshold(gray, 230, 255, cv2.THRESH_BINARY)[1] #  contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_TC89_L1)[1] if not num: #        return sorted(contours, key = cv2.contourArea, reverse = True)[1] else: # ars.n      main() return sorted(contours, key = cv2.contourArea, reverse = True)[1:num+1] def rotate(contour, src): #   angle = cv2.minAreaRect(contour)[2] if angle > 45: angle -= 90 if angle < -45: angle += 90 #    w, h = src.shape[1], src.shape[0] #    rotangle = deg2rad(angle) #      nw = abs(sin(rotangle)*h) + abs(cos(rotangle)*w) nh = abs(cos(rotangle)*h) + abs(sin(rotangle)*w) #   rotation_matrix = cv2.getRotationMatrix2D((nw*0.5, nh*0.5), angle, 1.0) rotatiom_move = dot(rotation_matrix, nparray([(nw-w)*0.5, (nh-h)*0.5,0])) rotation_matrix[0,2] += rotatiom_move[0] rotation_matrix[1,2] += rotatiom_move[1] #  src = cv2.warpAffine(src, rotation_matrix, (int(ceil(nw)), int(ceil(nh))), flags=cv2.INTER_LANCZOS4, borderValue=(255, 255, 255)) #    ca = int0(cv2.boxPoints(cv2.minAreaRect(get_contours(src)))) #   , ""  return src[ca[2][1]+14:ca[0][1]-3, ca[1][0]+3:ca[3][0]-3] def main(): parser = ArgumentParser(description='   ') parser.add_argument('-n', type=int, dest='number', required=True, help='   ') parser.add_argument('-i', dest='image', required=True, help='   ') args = parser.parse_args() folder = dirname(args.image) image_name = basename(args.image) extension = image_name.split('.')[-1] image_name_without_extension = '.'.join(image_name.split('.')[:-1]) if folder:#    -  cwd chdir(folder)#   if not isdir(image_name_without_extension): mkdir(image_name_without_extension)#        #  image = cv2.imread(image_name) #   contours = get_contours(image, args.number) i = 1# for c in contours: # np.     ca = int0(cv2.boxPoints(cv2.minAreaRect(c))) #      im = image[ca[2][1]:ca[0][1], ca[1][0]:ca[3][0]] #  im = rotate(ca, im) #   cv2.imwrite(path_join(image_name_without_extension, '%s.%s'%(i, extension)), im) #  i += 1 if __name__=='__main__': main()
      
      








All Articles