画像がカラーか、白黒かを判別する方法では、偶然にはてな質問の回答を見つけたので、コピペで行っていた。正直言って何がどうなってるのかよくわかってなかった。自宅の書籍や書類を整理するために大量にスキャンしていると、やっぱりこの判定がほしいなと思った。そのためもう一度見直すことにした。
画像がカラー・モノクロを判定する。
convert コマンドで画像を変換する、閾値より上下で2値にする、最後にmean(平均)を取り出す。
convert 001.jpg \ -colorspace HSB \ -separate \ -delete 0 \ -fx "u*v" \ -blur 2x2 \ -threshold 30% \ -format '%[fx:mean]\n' info:
もともとの回答者のコメントをみると、Convertで何をしているのかが見える。
colorspace HSB : 色空間をHSBに
separate : HSBになってる各チャンネルを分離して3枚のモノクロ画像に
delete 0 : 最初の0番の画像(色相)を削除
fx "u*v" : 1番(彩度)と2番(明度)の画像の各ピクセルを掛け合わせる。これで円柱型から円錐型のHSB空間における彩度になる
blur 2x2 : ノイズ除去用にぼかし
threshold 30% : 彩度30%を閾に二値化
format '%[fx:mean]' : 全体の平均値。30%で二値化しているので30%以上となったピクセルの割合になる。
書籍の自炊画像から、カラーページとモノクロページを判別するプ… - 人力検索はてな
サンプル画像を見ながら試す。
手順通りにやってみる。
元画像
HSBにする
RGBをHSBにする。(画像自体は変わらない。)
convert 001.jpg \ -colorspace HSB \ a-color.jpg
セパレートする
カラースペース(HSB:Hue色相・Saturation彩度・Brightness明度)に分割した状態で保存する。3枚になる。
convert 001.jpg \ -colorspace HSB \ -separate\ b-color.jpg
b-color-0.jpg (Hue)
b-color-1.jpg (Sat)
b-color-1.jpg (Bri/V)
Hueを削除
上記のところから、インデックス=0であるHueを削除したもの
convert 001.jpg \ -colorspace HSB \ -separate \ -delete 0 \ c-color.jpg
2枚できる。出力に変化がないので、サンプル省略。
2枚を乗算
S,B の2枚を乗算する。オーバーレイ。彩度を外したので、色がないものになる。
convert 001.jpg \ -colorspace HSB \ -separate \ -delete 0 \ -fx 'u*v'\ d-color.jpg
ぼかす。
細かいところがノイズにならないようちょっとぼかす
convert 001.jpg \ -colorspace HSB \ -separate \ -delete 0 \ -fx 'u*v' \ -blur 2x2 \ e-color.jpg
( blur 2,2 程度ではあまり変化はない、逆に考えると、見た目には変わらない程度に、ゴミが除去されたということ)
30% で2値化
30% ( 色範囲 0-65535の30%なので、19660>x , 19660<x ) の2値に分割する。
convert 001.jpg \ -colorspace HSB \ -separate \ -delete 0 \ -fx 'u*v' \ -blur 2x2 \ -threshold 30% \ f-color.jpg
指定した閾値より濃いところは黒に、薄いところは白に2値化されている。
全体の平均を取る。
この状態で、全体の平均(白黒のピクセル数を集計し平均を取る、ツイッタのサムネイルの背景色、dominant_colorsを計算するようなもの)
convert 001.jpg \ -colorspace HSB \ -separate \ -delete 0 \ -fx 'u*v' \ -blur 2x2 \ -threshold 30% \ -format '%[fx:mean]\n'\ info:
結果は次のようになっている。
0.216293
これは、0-65535の20(=14174)である。
この結果から、2値化して、色数が多い事がわかるので、カラーであるとわかる。
ぎゃくに、書籍のように白背景に黒文字、など色数が少ないものは、もっと小さな値になる。
書籍の例
たとえば、次のような画像で計算すると
convert dora.jpg -colorspace HSB -separate -delete 0 -fx 'u*v' -blur 2x2 -threshold 30% -format '%[fx:mean]\n' info:
結果は、ゼロである。
0
次の画像は、0.21916 である。
このことから、画像が白黒に近いほど数字は0にちかづき、30%(0.3)を閾値にしているので、0.3より小さければ、ほぼ線画であるようなきがする。
ruby でやってみる。
このヒューリスティックなアルゴリズムで本当に大丈夫なのか。画像を変えて調べたい。
大量に調べると画像の変換や一時ファイルへの書き出しがめんどくさいので、プログラムからオンメモリで処理していきたい。
とりあえず、手元ですぐ使えそうなruby を使ってみる。
mkdir work cd work bundle init sudo apt install libmagickwand-dev bundle add pry bundle add rmagick
コードを書いてみる。
#!/usr/bin/env ruby require 'rmagick' require 'pry' def mean(file) blob = File.open(file).read i = Magick::Image.from_blob(blob).first i.resize_to_fit!(600) i.colorspace=Magick::HSBColorspace h = i.channel(Magick::HueChannel) v = i.channel(Magick::LuminosityChannel) u = i.channel(Magick::SaturationChannel) h.write('x-b-color-0.jpg') u.write('x-b-color-1.jpg') v.write('x-b-color-2.jpg') i = u.composite(v,0,0,Magick::MultiplyCompositeOp) i.write('x-d-color.jpg') i.blur_image(2,2) i.write('x-e-color.jpg') f = i.dup.bilevel_channel( Magick::QuantumRange * 0.3 ) f.write('x-f-color.jpg') r = f.channel_mean(Magick::GrayChannel).map { |x| x / Magick::QuantumRange }[0] end (1..30).each{|i| f = sprintf('%03d.jpg',i) r = mean(f) puts sprintf("%5s: %05f", f, r ) }
スキャン・スナップで取り込んだ書籍で試してみる。 実行してみる
bundle exec ruby rmagick.rb 001.jpg: 0.114141 002.jpg: 0.000000 003.jpg: 0.000000 004.jpg: 0.000000 005.jpg: 0.000000 006.jpg: 0.000000 007.jpg: 0.000000 008.jpg: 0.000000 009.jpg: 0.000000 010.jpg: 0.000000 011.jpg: 0.000000 012.jpg: 0.000000 013.jpg: 0.000000 014.jpg: 0.000000 015.jpg: 0.000000 016.jpg: 0.000000 017.jpg: 0.000000 018.jpg: 0.000000 019.jpg: 0.000000 020.jpg: 0.000000 021.jpg: 0.000000 022.jpg: 0.000000 023.jpg: 0.000000 024.jpg: 0.000000 025.jpg: 0.000000 026.jpg: 0.000000 027.jpg: 0.000000 028.jpg: 0.000000 029.jpg: 0.000000 030.jpg: 0.019163
あー良いなこれ。
ruby rmagick と convert のちがい
チャンネルやカラースペースを入れると、ImageListになる。
thredshold の箇所が、2値化を示す bi-level-channel という、わかりやすい名前になっている(しかしその分調べにくい。)
i.dup.bilevel_channel( Magick::QuantumRange * 0.3 )
なるほど、ね。
MagickよりOpenCVで調べたほうが楽だったか。
rmagick で画像処理してる人いなくて調べるのめんどくさかった。OpenCVでPythonにすればよかった。
rgb でグレースケールを判定
そもそも、画像がグレースケール(2値化)に処理されている、処理済みの画像であれば、もっと簡単なのですよね。
#!/usr/bin/env ruby require 'rmagick' require 'pry' def rgb(file) blob = File.open(file).read i = Magick::Image.from_blob(blob).first i.resize_to_fit!(600) r = i.channel(Magick::RedChannel) g = i.channel(Magick::GreenChannel) b = i.channel(Magick::BlueChannel) return r == g && g == b end (1..30).each{|i| f = sprintf('%03d.jpg',i) rr = rgb(f) ? 'gray': 'color' puts sprintf("%5s: %5s", f, rr ) }
たしかに、もともとの画像がすでにグレースケールであれば、ピクセル計算でチェックせずに簡単に判定が可能ですね。
参考資料
https://stackoverflow.com/questions/21294036/
https://teratail.com/questions/372846