opencv チュートリアルチャレンジ9 OpenCVにおける輪郭(領域)

輪郭に関するチュートリアル

OpenCVにおける輪郭(領域) — OpenCV-Python Tutorials 1 documentation

輪郭抽出

OpenCVの輪郭検出は,黒い背景から白い物体の輪郭を検出すると仮定しています.物体は白(明るい色),背景は黒(暗い色)と覚えておいてください.

とある。

cv2.findContours

輪郭抽出と、その輪郭に対する処理を人多りやてみる。

void findContours(const Mat& image, vector<vector<Point> >& contours, int mode, int method, Point offset=Point())
2値画像中の輪郭を検出します.

パラメタ:   
image – 入力画像,8ビット,シングルチャンネル.0以外のピクセルは 1として,0のピクセルは0のまま扱われます.つまり,入力画像は 2値画像 として扱われます.グレースケールやカラー画像から2値画像を得るには, compare() , inRange() , threshold() , adaptiveThreshold() , Canny() などの関数を利用します.また,この関数は,輪郭抽出処理中に入力画像 image の中身を書き換えます.
contours – 検出された輪郭.各輪郭は,点のベクトルとして格納されます.
hiararchy – オプション.画像のトポロジーに関する情報を含む出力ベクトル.これは,輪郭数と同じ数の要素を持ちます.各輪郭 contours[i] に対して,要素 hierarchy[i][0] , hiearchy[i][1] , hiearchy[i][2] , hiearchy[i][3] にはそれぞれ,同じ階層レベルに存在する前後の輪郭,最初の子輪郭,および親輪郭の contours インデックス(0 基準)がセットされます.また,輪郭 i において,前後,親,子の輪郭が存在しない場合,それに対応する hierarchy[i] の要素は,負の値になります.
mode –
輪郭抽出モード
CV_RETR_EXTERNAL 最も外側の輪郭のみを抽出します.すべての輪郭に対して hierarchy[i][2]=hierarchy[i][3]=-1 がセットされます.
CV_RETR_LIST すべての輪郭を抽出しますが,一切の階層構造を保持しません.
CV_RETR_CCOMP すべての輪郭を抽出し,それらを2階層構造として保存します:上のレベルには,連結成分の外側の境界線が,下のレベルには,連結成分の内側に存在する穴の境界線が属します.ある連結成分の穴の内側に別の輪郭が存在する場合,その穴は上のレベルに属します.
CV_RETR_TREE すべての輪郭を抽出し,入れ子構造になった輪郭を完全に表現する階層構造を構成します.この完全な階層構造は,OpenCVの contours.c デモで見ることができます.
method –
輪郭の近似手法:
CV_CHAIN_APPROX_NONE すべての輪郭点を完全に格納します.つまり,この手法により格納された任意の隣り合う2点は,互いに8近傍に存在します.
CV_CHAIN_APPROX_SIMPLE 水平・垂直・斜めの線分を圧縮し,それらの端点のみを残します.例えば,まっすぐな矩形の輪郭線は,4つの点にエンコードされます.
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS Teh-Chinチェーン近似アルゴリズムの1つを適用します. TehChin89 を参照してください.
offset – オプションのオフセット.各輪郭点はこの値の分だけシフトします.これは,ROIの中で抽出された輪郭を,画像全体に対して位置づけて解析する場合に役立ちます.

通常、グレースケール化して、輪郭を抽出して、見やすいように色を載せると思う。

面倒なので、カラー化したものを変換して乗せてしまう。

元の画像は、これ。 f:id:pongsuke:20170519181729j:plain

#!/usr/bin/env python
# -*- coding: utf-8 -*
import sys
import cv2
import numpy as np
import math

img = cv2.imread('170519-174830.jpg')
img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

img = cv2.GaussianBlur(img, (11, 11), 0)
#cv2.imshow('GaussianBlur', img)

#ret, img = cv2.threshold(img, 60, 255, cv2.THRESH_BINARY)
#cv2.imshow('threshold1', img)
#cv2.imwrite('threshold1.png', img)

#ret, img = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY)
#cv2.imshow('threshold2', img)
#cv2.imwrite('threshold2.png', img)

img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 15, 5)
cv2.imshow('adaptiveThreshold', img)
#cv2.imwrite('adaptiveThreshold.png', img)

#img, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
img, contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print("{} contours.".format(len(contours)))

# カラー化
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)

#img = cv2.drawContours(img, contours, -1, (0,0,255), 1)
#cv2.imshow('drawContours', img)
#cv2.imwrite('drawContours.png', img)

if len(contours) > 0:
    for i, contour in enumerate(contours):
        #print("{}, {}".format(i, len(contour)))

        # area:面積
        area = cv2.contourArea(contour)
        if area < 100:
            continue

        # moments:重心
        M = cv2.moments(contour)
        if M["m00"] != 0:
            cx = int(M["m10"] / M["m00"])
            cy = int(M["m01"] / M["m00"])
        else:
            cx, cy = 0, 0
        # 重心を中心にした円
        cv2.circle(img, (cx, cy), int(math.sqrt(area)), (0,255,0), 1, 8)

        # 最小外接円
        (x, y), radius = cv2.minEnclosingCircle(contour)
        center = (int(x),int(y))
        radius = int(radius)
        img = cv2.circle(img,center,radius,(255,255,0),2)

        # 輪郭の近似
        epsilon = 0.05*cv2.arcLength(contour,True)
        approx = cv2.approxPolyDP(contour,epsilon,True)
        print(len(approx))
        if len(approx) == 4:
            cv2.drawContours(img, [approx], -1, (255, 0, 0), 3)

        # 凸包
        hull = cv2.convexHull(contour)
        img = cv2.drawContours(img,[hull],0,(255,255,0),2)

        # 外接矩形
        rect = cv2.boundingRect(contour)
        cv2.rectangle(img, (rect[0], rect[1]), (rect[0]+rect[2], rect[1]+rect[3]), (0,0,255), 1)

        # 回転を考慮した外接矩形
        rect = cv2.minAreaRect(contour)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        img = cv2.drawContours(img,[box],0,(0,255,255),1)

        # 楕円のフィッティング
        ellipse = cv2.fitEllipse(contour)
        img = cv2.ellipse(img,ellipse,(0,255,0),2)

        #直線のフィッティング
        rows,cols = img.shape[:2]
        [vx,vy,x,y] = cv2.fitLine(contour, cv2.DIST_L2,0,0.01,0.01)
        lefty = int((-x*vy/vx) + y)
        righty = int(((cols-x)*vy/vx)+y)
        img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)

cv2.imshow('findContours', img)
cv2.imwrite('findContours.png', img)

cv2.waitKey(0)
cv2.destroyAllWindows()

こうなった f:id:pongsuke:20170519182204p:plain

キーワード

  1. アスペクト比

  2. Extent

  3. Solidity

  4. 等価直径

  5. 傾き

  6. マスクと画素点

  7. 最大値,最小値とその位置

  8. 平均色と平均値

  9. 端点

凸性の欠陥

こんどやる

輪郭の階層情報

いつかやる