はじめに
アプリ実装をしていると、たまに下記のような画像を組み込む事になったりします。
※わかりにくいので、右に透過されている部分を黒で塗りつぶしたものも用意しました。
画面サイズの透過画像に対し、表示されて欲しい所にアイコンが配置されたものです。
これiPhoneならまだ良いのですが、Androidはそうもいきません、これ読み込んだらOOMまっしぐらです。 しかも数枚くらいなら良いかもしれませんが、今回これをアニメーションさせる上に、一度に10枚以上読み込む必要がありました。
それは無理なので、とりあえず上記画像を下記のように、出来るだけ余計な透過部分は省いて利用する必要があります。
まあ、10枚くらいなら自力で切り出せば良いのですが、今回400枚以上あるんです。 そんなの自力でやってたら気を失いそうなので、自動でできないかなー、とやってみたら出来ました。
OpenCVの利用する
OpenCVでは画像内の外接矩形を取得できるので、その中で一番外側に接している矩形を出します。今回は実行するのが楽なrubyで実装しました。
と、その前にMacはrubyが入っていますが、OpenCV等は入ってないので入れます。例のごとくhomebrewを使います。
1 2 3 4 5 6 7 8 |
$ brew doctor $ brew update $ brew tap homebrew/science $ brew install opencv $ sudo gem install ruby-opencv $ sudo gem instal mini_magick ※今回最後に画像を切り出すのためにimagemagickを使ってます。 imagemagickが無い場合、$ brew install imagemagick |
そしたら下記スクリプトを作ります、多少雑ですが・・・
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# cut_img.rb require 'opencv' require 'mini_magick' # 初期化 before_dir = "" before_name = "" origin_csv = "" # imagesフォルダ内の全ての.pngのパスを取得しループ paths = Dir.glob("./images/**/*.png") paths.each do |path| puts path # 画像読み込み rgbmat = OpenCV::CvMat.load(path) # グレイスケールに変更 graymat = rgbmat.BGR2GRAY # 画像のエッジ検出 canny = graymat.canny(160, 200) # 取得したエッジの外接矩形を取得 contour = canny.find_contours(:mode => OpenCV::CV_RETR_EXTERNAL, :method => OpenCV::CV_CHAIN_APPROX_TC89_L1) rect = OpenCV::CvRect.new(rgbmat.width, rgbmat.height, 0, 0); # 矩形の中で最も外側にある矩形を探しだす while contour unless contour.hole? box = contour.bounding_rect if rect.x > box.x rect.x = box.x end if rect.y > box.y rect.y = box.y end if rect.width < box.x + box.width rect.width = box.x + box.width end if rect.height < box.y + box.height rect.height = box.y + box.height end end contour = contour.h_next end # width等に入っているのはx+widthの値なので、xを引く rect.width = rect.width - rect.x; rect.height = rect.height - rect.y; # もし画像よりはみ出していたら修正する if rect.x + rect.width > rgbmat.width rect.width = rgbmat.width - rect.x end if box.y + rect.height > rgbmat.height rect.height = rgbmat.height - rect.y end if rect.x < 0 rect.x = 0 end if box.y < 0 box.y = 0 end puts "rect #{rect.x}, #{rect.y}, #{rect.width}, #{rect.height}" # 作った外接矩形で画像を切り抜く img = MiniMagick::Image.open path img.format('png') img.crop("#{rect.width}x#{rect.height}+#{rect.x}+#{rect.y}") save_path = path.sub("images/", "result/") # ディレクトリも大文字を維持する場合 # write_dir = File.dirname(save_path) # ディレクトリも小文字にする場合 今回Androidで使うので全部小文字に変更 write_dir = File.dirname(save_path).downcase # resultディレクトリ内に同じディレクトリを作り画像を保存 FileUtils.mkdir_p(write_dir) img.write("#{write_dir}/#{File.basename(save_path).downcase}") # 対象のディレクトリ内での作業が終わったら、外接矩形情報をcsvとして保存する if before_dir != "" && before_dir != write_dir file_name = "rect.csv" #保存するファイル名 File.open("#{before_dir}/#{before_name[/(.*)_/,1]}_rect.csv", 'w') {|file| file.write origin_csv } origin_csv = "" before_dir = write_dir before_name = File.basename(save_path).downcase end if before_dir == "" before_dir = write_dir before_name = File.basename(save_path).downcase end # csvデータの生成 origin_csv = "#{origin_csv}#{rect.x},#{rect.y},#{rect.width},#{rect.height}\n" end # 最後のcsv情報を保存 file_name = "rect.csv" #保存するファイル名 File.open("#{before_dir}/#{before_name[/(.*)_/,1]}_rect.csv", 'w') {|file| file.write origin_csv } |
処理に関してはコメントの通りです。
これを下記の様にディレクトリを作って、imagesディレクトリに切り出したいpng画像を入れます。
ちなみにディレクトリが切られていても大丈夫です。 そしたらターミナルでcut_img.rbを実行します。
1 |
$ ruby cut_img.rb |
あとは待つだけです、処理が終われば切りだされたpngがresultディレクトリに入ります。 ちなみにrect.csvというものも一緒に生成されますが、下記のようなデータが入ります。
1 |
99,183,161,137 |
これは切り出し前の画像における切り出した部分の矩形情報です。
最後に
恐らく手動でやってたら1〜2日かかりましたが、このスクリプト作ったのは2時間くらいで、400枚実行しても数分で終わるので結構短縮できました。 もしデザイン変更が来てもこれがあれば気を失うような作業をしなくても良いでしょう。
こういうのを作るとプログラムした感があって良いですね。