OpenCVは行列を表すクラスを独自に持っていますが(cv::Mat
クラス)、Pythonバインディングではこれをnumpyのndarrayに置き換えています。opencvrはcv::Mat
をRubyにバインドさせたものを使っていましたが、試しにNumo::NArrayにバインドさせてみました(opencvrとは何か、とかOpenCVの行列ライブラリの話の詳細は以前に書いたこの記事を参照)。
具体的にはopencvr内で以下の2つを行います。
- OpenCVの関数の戻り値が
cv::Mat
のときは、その戻り値をNumo::NArrayに変換してRuby側に返す
- OpenCVの関数の引数が
cv::Mat
のときは、Ruby側から受け取った引数はNumo::NArrayであると想定し、それをcv::Mat
に変換してからC++の関数を呼び出す
試しに実装しただけなので不完全なところはあるでしょうが一応動きました。以下サンプル。
元画像
読み込みと行列の情報の表示
imread()
の戻り値がNumo::UInt8になっています。
#!/usr/bin/env ruby
require 'numo/narray'
require_relative '../opencvr/cv2'
img = CV2::imread("sample1.jpg")
p img.class
p img.shape
p img.size
# 実行結果
$ ./test.rb
Numo::UInt8
[533, 800, 3]
1279200
減色処理
Numo::NArrayのAPIで画像を加工してみます。画素値を整数値で割ると小数点以下が切り捨てられ、また同じ値を掛けると値が離散的になり、結果として減色されます(もっと良いアルゴリズムがあるだろうけど)。
また、hstack()
で行列を結合させることで画像の結合も可能です。
#!/usr/bin/env ruby
require 'numo/narray'
require_relative '../opencvr/cv2'
img = CV2::imread("sample1.jpg")
img2 = img / 32 * 32
img3 = Numo::NArray::hstack([img, img2])
CV2::imwrite("subtract.jpg", img3)
画像をグレースケールにしてヒストグラムを作成
画像読み込み時にIMREAD_GRAYSCALE
を指定するとグレースケールとして読み込み、結果は2次元UInt8行列になります。0~255がそれぞれ何ピクセルあるかヒストグラムで表示します。Numo::GSLとNumo::Gnuplotを使ったヒストグラムの作成はこちらの記事を参考にしました。
#!/usr/bin/env ruby
require 'numo/narray'
require_relative '../opencvr/cv2'
require 'numo/gnuplot'
require 'numo/gsl'
def gen_hist(img, fname)
h = Numo::GSL::Histogram.new(256)
h.set_ranges_uniform(0, 255)
img_flat = img.flatten
img_flat.each do |x|
h.increment x
end
freq = h.bin
center_of_range = h.range.to_a.each_cons(2).map{|a,b| (a+b)/2.0}
Numo.gnuplot do
set terminal: :png
set output: fname
set style:[:fill, :solid]
unset :key
plot center_of_range, freq, w: :boxes
end
end
img_gray = CV2::imread("sample1.jpg", CV2::IMREAD_GRAYSCALE)
gen_hist(img_gray, "hist-rb.png")
なお、Pythonならもっと短いコードで同じような結果が得られます。Rubyももっと短くしたいところですが。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("sample1.jpg", cv2.IMREAD_GRAYSCALE)
img2 = img.flatten()
plt.hist(img2, bins=256)
plt.savefig('hist-py.png')
ベタ塗り画像に文字を書く
今度はimread()
で画像ファイルを読み込むのではなく、Numo::NArrayのAPIで行列データを作成します。カラー画像を作成するには3次元のUInt8型の行列を作ります。幅800, 高さ600ピクセルの画像なら(600, 800, 3)です。このうち青成分のみ255にすれば青ベタ塗りの画像ができるので、それにputText()
で文字を書きこみます。なおOpenCVの画像データはRGBではなくBGRなので、青成分は0番です。
#!/usr/bin/env ruby
require 'numo/narray'
require_relative '../opencvr/cv2'
img = Numo::UInt8.zeros(600, 800, 3)
img[0..-1, 0..-1, 0] = 255
CV2::putText(img, "Hello OpenCV with Numo::NArray", [100, 300], CV2::FONT_HERSHEY_DUPLEX, 1, [255, 255, 255])
CV2::imwrite("out.png", img)
ちなみに
画像をNumo::NArrayで取得するだけならすでにmagroというライブラリがあるのでそっちを使った方が良いです。あと単なる減色処理や画像の結合についてももっと良い既存のライブラリがあるはずです。この程度の処理にOpenCVを使うのはオーバーキルですね。