Thursday, February 19, 2015

Python版OpenCVで色抽出を行う

今回は,Python版のOpenCVを使ってHSV色空間で色抽出をする方法をメモしておきます.

環境は以下のとおりです.
Python 2.6
OpenCV 2.4.6

更新履歴
  • 2015/03/12
    しきい値処理におけるTHRESH_BINARYとTHRESH_BINARY_INVが逆になっているのを修正しました.

それでは早速スクリプトを見ていきます.

import cv2

def extract_color( src, h_th_low, h_th_up, s_th, v_th ):

    hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
    h, s, v = cv2.split(hsv)

    if h_th_low > h_th_up:
        ret, h_dst_1 = cv2.threshold(h, h_th_low, 255, cv2.THRESH_BINARY) 
        ret, h_dst_2 = cv2.threshold(h, h_th_up,  255, cv2.THRESH_BINARY_INV)
        
        dst = cv2.bitwise_or(h_dst_1, h_dst_2)

    else:
        ret, dst = cv2.threshold(h,   h_th_low, 255, cv2.THRESH_TOZERO) 
        ret, dst = cv2.threshold(dst, h_th_up,  255, cv2.THRESH_TOZERO_INV)

        ret, dst = cv2.threshold(dst, 0, 255, cv2.THRESH_BINARY)
        
    ret, s_dst = cv2.threshold(s, s_th, 255, cv2.THRESH_BINARY)
    ret, v_dst = cv2.threshold(v, v_th, 255, cv2.THRESH_BINARY)

    dst = cv2.bitwise_and(dst, s_dst)
    dst = cv2.bitwise_and(dst, v_dst)

    return dst

色抽出する処理を関数にまとめてみました.
この関数の戻り値と引数は以下のようになります.黒を除外して色相で抜きたい色を指定するイメージで処理を行います.
出力画像 extract_color( 入力画像, 色相のしきい値(下), 色相のしきい値(上), 彩度のしきい値, 明度のしきい値 )
関数の内部の処理を説明します.
入力画像はRGBカラーモデルであることを想定し,HSV色空間に変換します.
その後,HSVの各チャンネルを別々の画像にわけます.
hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
赤色を抽出する場合は,色相が360°をまたぐ可能性があるため,ここで処理を分けます.
色相のしきい値(下)がしきい値(上)より大きいというのは,赤のように350°から10°までというような場合を想定しています.
360°を境界として2回しきい値処理を行った後に各画像を合わせることで抽出します.
まず,以下のようなcv2.threshold関数を使って,しきい値処理を行います.
cv2.threshold(画像, しきい値, 最大値, しきい値処理のタイプ)
しきい値処理のタイプは以下のものを使用します.

cv2.THRESH_BINARY: しきい値より大きい値は最大値,それ以外は0
cv2.THRESH_BINARY_INV: しきい値より大きい値は0,それ以外は最大値

その後,各画像のORを取ることで,2枚の画像を合わせます.
    if h_th_low > h_th_up:
        ret, h_dst_1 = cv2.threshold(h, h_th_low, 255, cv2.THRESH_BINARY) 
        ret, h_dst_2 = cv2.threshold(h, h_th_up,  255, cv2.THRESH_BINARY_INV)
        
        dst = cv2.bitwise_or(h_dst_1, h_dst_2)
次に,360°をまたがない場合を考えます.
ここでは,しきい値処理に以下のものを使用します.

cv2.THRESH_TOZERO: しきい値よりより大きい値はそのまま,それ以外は0
cv2.THRESH_TOZERO_INV: しきい値よりより大きい値は0,それ以外はそのまま

まず,しきい値(下)の値を0にし,その後,しきい値(上)より大きい値を0にすることで特定の範囲の値を抽出できます.
    else:
        ret, dst = cv2.threshold(h,   h_th_low, 255, cv2.THRESH_TOZERO) 
        ret, dst = cv2.threshold(dst, h_th_up,  255, cv2.THRESH_TOZERO_INV)

        ret, dst = cv2.threshold(dst, 0, 255, cv2.THRESH_BINARY)
色相については,ここまでで処理できたので,残りの明度と彩度についても処理を行います.
ここでは,黒に近い色を排除するだけでよいので,下限値のみのしきい値処理を行います.
    ret, s_dst = cv2.threshold(s, s_th, 255, cv2.THRESH_BINARY)
    ret, v_dst = cv2.threshold(v, v_th, 255, cv2.THRESH_BINARY)
そして最後に,各画像のANDをとり,各しきい値を満たす画素を抽出します.
    dst = cv2.bitwise_and(dst, s_dst)
    dst = cv2.bitwise_and(dst, v_dst)
これで,任意の色を抽出するextract_color関数が完成しました.

それでは,この関数を使って色抽出を行ってみます.
基本的なWebカメラの扱い使い方は,ここを見てください.
if __name__=="__main__":

    capture = cv2.VideoCapture(0)
    
    if capture.isOpened() is False:

        raise("IO Error")

    cv2.namedWindow("Capture", cv2.WINDOW_AUTOSIZE)
    cv2.namedWindow("Red",     cv2.WINDOW_AUTOSIZE)
    cv2.namedWindow("Yellow",  cv2.WINDOW_AUTOSIZE)

    while True:

        ret, image = capture.read()
        if ret == False:
            continue

        red_image    = extract_color(image, 170, 5,  190, 200)
        yellow_image = extract_color(image, 10,  25, 50,  50)

        cv2.imshow("Capture", image)
        cv2.imshow("Red",     red_image)
        cv2.imshow("Yellow",  yellow_image)
       
        if cv2.waitKey(33) >= 0:
            cv2.imwrite("image.png", image)
            cv2.imwrite("red_image.png", red_image)
            cv2.imwrite("yellow_image.png", yellow_image)
            break

    cv2.destroyAllWindows()
このスクリプトの実行結果は以下のようになります.
まずは,入力画像です.
image
次に,赤色の抽出結果です.
red_image
最後に,黄色の抽出結果です.
yellow_image

この例では,赤色と黄色をそれぞれ抽出して画像を表示しています.
各画像を見ると,それぞれの色で抽出出来ていることがわかります.
基本的な処理は既に説明していますが,OpenCVではHSVの各チャンネルは以下のような値域となっていることに注意してください.

H(色相):0~180
S(彩度):0~255
V(明度):0~255

以上でHSV色空間で色抽出をする方法の説明を終わります.  

Python版OpenCV関連の記事:
Python版OpenCVでWebカメラの画像を取得する
Python版OpenCVで色抽出を行う

参考文献:
「Miscellaneous Image Transformations」『OpenCV 2.4.9.0 documentation』<http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html> (2015/02/20アクセス)
「Basic Operations on Images」『OpenCV 3.0.0-dev documentation』<http://docs.opencv.org/trunk/doc/py_tutorials/py_core/py_basic_ops/py_basic_ops.html> (2015/02/20アクセス) 

Friday, February 13, 2015

Python版OpenCVでWebカメラの画像を取得する

OpenCVを使うときは主にC++を使っているのですが,Pythonでも使えると何かと便利なので使い方を調べてみました.

今回は,Webカメラから画像を取得する方法をメモしておきます.
ちなみにPython 2.6を使っている関係で,OpenCV 2.4.6を使っています.

それでは早速スクリプトを見ていきます.
import cv2

if __name__=="__main__":

    capture = cv2.VideoCapture(0)
    
    if capture.isOpened() is False:
        raise("IO Error")

    cv2.namedWindow("Capture", cv2.WINDOW_AUTOSIZE)

    while True:

        ret, image = capture.read()

        if ret == False:
            continue

        cv2.imshow("Capture", image)
       
        if cv2.waitKey(33) >= 0:
            cv2.imwrite("image.png", image)
            break

    cv2.destroyAllWindows()

まずは,0番のカメラを引数にしてVideoCaptureクラスをインスタンス化します.
capture = cv2.VideoCapture(0)
次に,isOpenedメソッドを使ってWebカメラに接続できているか確認します.
もしできていなければIOエラーをだします.
if capture.isOpened() is False:
        raise("IO Error")
Webカメラで取得した画像を表示するCaptureという名前のウィンドウを設定します.
cv2.WINDOW_AUTOSIZEを指定すると,表示する画像に応じてウィンドウの大きさが自動的に決まります.
cv2.namedWindow("Capture", cv2.WINDOW_AUTOSIZE)
この先の処理は後ほど説明するキー入力があるまで,処理をwhileループで繰り返します.
まずは,画像をWebカメラから取得します.
取得できればimageにnumpyのndarray形式で画像が,retにbool形式でTrueが格納されます.
ret, image = capture.read()
もし,retにFalseが入っていればこの後の処理を飛ばします.
if ret == False:
            continue
取得した画像を先ほど生成したウィンドウCaptureに表示します.
cv2.imshow("Capture", image)
最後に,cv2.waitKeyを呼び出します.引数はウェイト時間 msです.0を指定すると無限にキー入力を待ちます.
ちなみにcv2.watiKeyはイベント処理を扱うので定期的に呼び出す必要があることに注意してください.
これを書かないとキー入力を受け取れないだけでなく,画像もウィンドウに表示されません.
ここでは,何かキー入力があった場合,image.pngというPNGファイルに画像を保存します.
if cv2.waitKey(33) >= 0:
            cv2.imwrite("image.png", image)
            break
キーが押された場合に,全てのウィンドウを破壊して終わります.
cv2.destroyAllWindows()
以上でWebカメラの画像取得方法の説明を終わります.  

Python版OpenCV関連の記事:
Python版OpenCVでWebカメラの画像を取得する
Python版OpenCVで色抽出を行う

参考文献:
「User Interface」『OpenCV 2.4.9.0 documentation』<http://docs.opencv.org/modules/highgui/doc/user_interface.html> (2015/02/14アクセス)
「Reading and Writing Images and Video」『OpenCV 2.4.9.0 documentation』<http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html> (2015/02/14アクセス) 

Sunday, February 8, 2015

ggplot2を使ってみる

最近,機械学習の勉強会でRをいじる機会がありました.
Rは機械学習や統計に関するパッケーが充実しているのは大変魅力的です.
そして,ggplot2という強力なグラフの描画用のパッケーがすごいです.
Excelではこんな見栄えのよいグラフを作れないよなと感動してしまいます.
今日はそんなRのggplot2で基本的な図の描画をしてみます.

まずggplot2をインストールしていない場合は,以下の用なコードでインストールしてください.
コードを入力するとCRAN mirrorというウィンドウが現れるので,Japanあたりのミラーを指定してください.
install.packages("ggplot2", dependencies = TRUE)
package ‘ggplot2’ successfully unpacked and MD5 sums checked
というメッセージがでればインストール成功です.
インストールが無事終わったら,以下のようにライブラリをロードします.
library('ggplot2')
まずは,データフレームを作ります.
set.seed(1)
x.1 <- rnorm(1000)
y.1 <- rnorm(1000)
label.1 <- ifelse(rbinom(1000, size = 1, prob = 0.5), TRUE, FALSE)

data.1 <- data.frame(X = x.1, Y = y.1, L = label.1)
x.1とy.1には正規分布に従う乱数を1000個ずつ生成して格納します.
label.1には,二項分布に従う乱数を生成し,1ならTRUE,0ならFALSEを格納します.
これらのベクトルからデータフレーム data.1を作成します.

データフレームの準備が出来たので,まずはXを0.4ずつに区切ってヒストグラムを描画します.
ggplot()にデータフレームと使用するデータのラベルを指定し,描画する環境を設定します.
その環境にgeom_histogram()で生成したヒストグラムを重ねることで,グラフを描画します.
hist.1 <- ggplot(data.1, aes(x = X)) + geom_histogram(binwidth = 0.4)
hist.1

次に,geom_density()を使って確率密度関数を描画します.
dens.1 <- ggplot(data.1, aes(x = X)) + geom_density()
dens.1

最後に,散布図を描画します.
x軸にラベルXのデータを,y軸にラベルYのデータ,各点の色にラベルLのデータを使用します.
散布図はgeom_point()関数で描画できます.
scatter.1 <- ggplot(data.1, aes(x = X, y = Y, color = L)) + geom_point()
scatter.1

ラベルごとに色が変わっており,さらに凡例もしっかりとついています.
軸のラベルや凡例のラベルを変更するには以下のように,labs()を使用します.
scatter.1 <- scatter.1 + labs(color = 'Label')

このように,よくあるグラフを簡単に,しかも見栄え良く描画することができます.

今度は,別のデータフレームを使って,任意の関数の描画方法を見てみましょう.
x.2 = runif(1000)
y.2 <- x.2 + rnorm(1000, sd = 0.05)

data.2 <- data.frame(X = x.2, Y = y.2)
y = xの1次関数に従うデータを生成します.
x.2には一様乱数を1000個生成して格納しています.
y.2にはx.2の値に標準偏差0.05,平均0のガウスノイズを加えたデータを格納しています.
まずは,このデータの散布図を描画します.
scatter.2 <- ggplot(data.2, aes(x = X, y = Y, color = L)) + geom_point()
scatter.2

次に,任意の関数をこの散布図に重ねてみます.
描画するのは,このデータの回帰直線を描画します.
回帰直線はlm()を使って以下のように簡単に求めることが出来ます.
fit <- with(data.2, lm(Y ~ X))
intercept <- coef(fit)[1]
slope <- coef(fit)[2]
この回帰直線をstat_function()関数の引数funに,ラムダ式を使って回帰直線の1次関数の計算式を与えます.
先ほどの散布図に重ねるには,この直線をscatter.2に足し合わせることで重ねることが出来ます.
scatter.2 <- scatter.2 + 
stat_function(colour = "orange", fun = function(x) slope * x + intercept, size = 1)
scatter.2

この回帰直線はy = xの関数にノイズを加えたものなので,回帰直線はy = xに近いことがわかります.
最後に,scatter.2を画像ファイルに保存します.
ggsave(plot = scatter.2, filename = "scatter_2.png", height = 8, width = 8)
以上のように,簡単に見栄えのするグラフが描画できました.
正直,ggplot2だけでもRを使う価値があると思います.
三次元のグラフのように見栄えのするグラフも描画できるようですので,みなさんも是非使ってみてください.

参考文献:
Drew Conwayら 『入門 機械学習』オライリージャパン (2012/12/22)
「乱数Tips大全」『RjpWiki』 (2015/02/09アクセス)

Tuesday, February 3, 2015

OpenCVでチェスボードの交点を検出する

最近OpenCVを使うことが増えたので,メモ代わりに情報を残しておきます.

今回は,OpenCVを使ってチェスボードの交点を探します.

以下の,チェスボードの交点を探します.
チェスボードのデータをPDFで置いておきますので,よろしければA4用紙に印刷して使用ください.
chess_board_A4_7x10.pdf

chess_A4_7x10

チェスボードの交点位置を求めるプログラムは以下のようになります.

#include "opencv2/core/core.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

bool find_chess_corners( cv::Mat image, 
                         cv::vector *corners, 
                         cv::Size pattern_size, cv::Size chess_size )
{
 bool is_find = cv::findChessboardCorners(image, pattern_size, *corners);

 if( is_find != true )
 {
  return false;
 }

 cv::Mat gray( image.rows, image.cols, CV_8UC1 );
 cv::cvtColor(image, gray, CV_BGR2GRAY);
 cv::cornerSubPix(gray, *corners, chess_size, 
                  cv::Size(-1, -1), 
                  cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.01));

 return true;
}

int main(int argc, const char ** argv)
{
 cv::Mat src_img = cv::imread("chess.jpg");

 cv::namedWindow("src", CV_WINDOW_AUTOSIZE);

 cv::vector src_corners;
 find_chess_corners( src_img, &src_corners, cv::Size(10, 7), cv::Size(20, 20) );
 cv::drawChessboardCorners( src_img, cv::Size(10, 7), ( cv::Mat )src_corners, true);

 cv::imshow("src", src_img);
 
 if( cv::waitKey(0) == 'q' )
 {
  cv::imwrite( "dst.jpg", src_img);
  return 0;
 }
} 

Visual Studioで実行する場合は,以下の記述をプログラムの最初に追記することで,必要なライブラリがリンクされます.
*2410は2.4.10版のことですので,ご自分の環境に合わせて書き換えてください.
#ifdef DEBUG
#pragma comment(lib,  "opencv_calib3d2410.lib")
#pragma comment(lib,  "opencv_core2410.lib")
#pragma comment(lib,  "opencv_highgui2410.lib")
#pragma comment(lib,  "opencv_imgproc2410.lib")
#else
#pragma comment(lib,  "opencv_calib3d2410d.lib")
#pragma comment(lib,  "opencv_core2410d.lib")
#pragma comment(lib,  "opencv_highgui2410d.lib")
#pragma comment(lib,  "opencv_imgproc2410d.lib")
#endif

このプログラムを実行すると,以下のようにチェスボードの交点を見つけることができます.

src

それではプログラムの説明を行います.
このプログラムは,読み込んだjpeg画像からチェスボードの交点を探します.
交点を探す処理はfind_chess_corners関数で行っています.
この関数の引数は以下のようになります.
bool fined_chess_corners( cv::mat カラー画像, 
                          cv::vector<cv::Point2f> 交点を格納する配列, 
                          cv::Size チェスボードの交点数(行, 列), 
                          cv::Size 探索ウィンドウの半分の大きさ) 
この関数内では,以下のような2つの関数を使って交点位置を求めています.

・cv::findChessboardCorners関数
チェスボードの交点は,cv::findChessboardCorners関数で見つけることができます.
チェスボードがあれば真を返し,なければ偽を返します.
そして,検出したチェスボードのコーナーが配列に格納されます.
cv::findChessboardCorners関数の引数は以下のようになります.
bool findChessboardCorners( cv::mat カラーまたは8bitグレースケール画像, 
                            cv::Size チェスボードの交点数(行, 列), 
                            cv::vector<cv::Point2f> 交点を格納する配列, 
                            int 処理のフラグ) 

cv::cornerSubPix関数
cv::cornerSubPix関数を使うことで検出した交点位置の精度をあげることができます.
cv::cornerSubPix関数の引数は以下のようになります.
対象外の探索領域の半分の大きさにcv::Size(-1, -1)を指定すると,対象外領域はないということを指定できます.
この関数はコーナー位置の高精度化処理を繰り返し行うので,その最大反復回数と要求精度のどちらか,または両方を設定します.
void cornerSubPix( cv::mat 8bitグレースケール画像, 
                   cv::vector<cv::Point2f> 交点を格納する配列, 
                   cv::Size 探索ウィンドウの半分の大きさ, 
                   cv::Size 対象外の探索領域の半分の大きさ, 
                   cv::TermCritera 繰り返し処理の終了条件) 
私もはまったのですが,cv::cornerSubPix関数に与える画像は1chのグレースケール画像である必要があるので注意してください.

これで,チェスボードのコーナー点を求めることができました.
今回は以上となります.

参考文献:
OpenCV 2 プログラミングブック制作チーム『OpenCV 2 プログラミングブック OpenCV 2.2/2.3対応』マイナビ (2011/12/27)
「特徴検出」『opencv 2.2 documetation』<http://opencv.jp/opencv-2svn/cpp/feature_detection.html> (2015/02/03アクセス)
「カメラキャリブレーションと3次元再構成」『opencv 2.2 documetation』<http://opencv.jp/opencv-2svn/cpp/camera_calibration_and_3d_reconstruction.html> (2015/02/03アクセス)