質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.52%

選択した範囲だけを検出したい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 5,551

snake207

score 11

やりたいこと

決められた範囲だけを背景差分法で動体物(テニスボール)を検出したい

 質問内容

自分で決めた範囲だけを処理するにはどうすればいいですか?

 現状

マウスで範囲を選択してボール検出(ボール以外も検出していますが...)ができるプログラムを作成しました。
しかし、マウスで選択した範囲だけ処理するというのができなかったため、自分で検出したい範囲(座標)を決めて、
その範囲だけを処理するプログラムに変更することにしました。そこで、処理する範囲をどのようなコードで決めて
処理をすればいいか分からない状態です。
※作成したプログラムは下記に載せておきます。

 プログラム

# -*- coding: utf-8 -*-

import numpy as np

import cv2

from time import time

import subprocess



VIDEO_DATE = "tennis.AVI"

WINDOW_NAME = "MouseEvent"



def dilation(dilationSize, kernelSize, img):  # 膨張した画像にして返す

    kernel = np.ones((kernelSize, kernelSize), np.uint8)

    element = cv2.getStructuringElement(cv2.MORPH_RECT, (2 * dilationSize + 1, 2 * dilationSize + 1), (dilationSize, dilationSize))

    dilation_img = cv2.dilate(img, kernel, element)

    return dilation_img




def detect(gray_diff, thresh_diff=95, dilationSize=9, kernelSize=20):  # 一定面積以上の物体を検出

    retval, black_diff = cv2.threshold(gray_diff, thresh_diff, 255, cv2.THRESH_BINARY)  # 2値化

    dilation_img = dilation(dilationSize, kernelSize, black_diff)  # 膨張処理

    img = dilation_img.copy()

  image, contours, hierarchy = cv2.findContours(dilation_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 境界線検出


  
    ball_pos = []



    for i in range(len(contours)):  # 重心位置を計算

        count = len(contours[i])


        area = cv2.contourArea(contours[i])  # 面積計算

        x, y = 0.0, 0.0

        for j in range(count):

            x += contours[i][j][0][0]

            y += contours[i][j][0][1]




        x /= count

        y /= count

        x = int(x)

        y = int(y)

        ball_pos.append([x, y])


    return ball_pos, img




def displayCircle(image, ballList, thickness=5):

    for i in range(len(ballList)):

        x = int(ballList[i][0])

        y = int(ballList[i][1])

        cv2.circle(image, (x, y), 10, (0, 0, 255), thickness)

    return image




def resizeImage(image, w=2, h=2):

    height = image.shape[0]

    width = image.shape[1]

    resizedImage = cv2.resize(image, (int(width / w), int(height / h)))

    return resizedImage




def blackToColor(bImage):

    colorImage = np.array((bImage, bImage, bImage))

    colorImage = colorImage.transpose(1, 2, 0)

    return colorImage



class PointList():

    def __init__(self, npoints):

        self.video = cv2.VideoCapture(VIDEO_DATE)

        self.frame = None

        self.npoints = npoints

        self.ptlist = np.empty((npoints, 2), dtype=int)

        self.pos = 0

        cv2.setMouseCallback(WINDOW_NAME, self.onMouse)



    def add(self, x, y):

        if self.pos < self.npoints:

            self.ptlist[self.pos, :] = [x, y]

            self.pos += 1

            return True

        return False



    def run(self):

        ok, self.frame = self.video.read()  # 最初のフレームを読み込む

        if not ok:

            print('Cannot read video file')

            sys.exit()

        self.frame_pre = self.frame.copy()



        while True:

            ok, self.frame = self.video.read()  # フレームを読み込む

            if not ok:

               break

            self.frame_next = self.frame.copy()



            if(self.pos == self.npoints):

                  cv2.line(self.frame, (self.ptlist[0][0], self.ptlist[0][1]),

                       (self.ptlist[1][0], self.ptlist[1][1]), (0, 255, 0), 3)

                  cv2.line(self.frame, (self.ptlist[1][0], self.ptlist[1][1]),

                       (self.ptlist[2][0], self.ptlist[2][1]), (0, 255, 0), 3)

                  cv2.line(self.frame, (self.ptlist[2][0], self.ptlist[2][1]),

                       (self.ptlist[3][0], self.ptlist[3][1]), (0, 255, 0), 3)

                  cv2.line(self.frame, (self.ptlist[3][0], self.ptlist[3][1]),

                       (self.ptlist[0][0], self.ptlist[0][1]), (0, 255, 0), 3)

            color_diff = cv2.absdiff(self.frame_next, self.frame_pre)  # フレーム間の差分計算

            gray_diff = cv2.cvtColor(color_diff, cv2.COLOR_BGR2GRAY)  # グレースケール変換

            retval, black_diff = cv2.threshold(gray_diff,30, 255, cv2.THRESH_BINARY)



            ball, dilation_img = detect(gray_diff)



            self.frame = displayCircle(self.frame, ball, 2)  # 丸で加工

            cImage = blackToColor(dilation_img)  # 2値化画像をカラーの配列サイズと同じにする



            self.frame_pre = self.frame_next.copy()  # 次のフレームの読み込み



            cv2.imshow(WINDOW_NAME, self.frame)

            if cv2.waitKey(10) ==27: # Escキーで抜ける

                break



      def onMouse(self,event, x, y, flag, params):

          if event == cv2.EVENT_MOUSEMOVE:  # マウスが移動したときにx線とy線を更新する

              self.frame2 = np.copy(self.frame)

              h, w = self.frame2.shape[0], self.frame2.shape[1]

              cv2.line(self.frame2, (x, 0), (x, h - 1), (255, 0, 0))

              cv2.line(self.frame2, (0, y), (w - 1, y), (255, 0, 0))

              cv2.imshow(WINDOW_NAME, self.frame2)



          if event == cv2.EVENT_LBUTTONDOWN:  # レフトボタンをクリックしたとき、ptlist配列にx,y座標を格納する

              if self.add(x, y):

                  print('[%d] ( %d, %d )' % (ptlist.pos - 1, x, y))

                  cv2.circle(self.frame, (x, y), 3, (0, 0, 255), 3)

                  cv2.imshow(WINDOW_NAME, self.frame)

              else:

                  print('All points have selected.  Press ESC-key.')



if __name__ == '__main__':

    cv2.namedWindow(WINDOW_NAME)

    npoints = 4

    ptlist = PointList(npoints)

    ptlist.run()

    cv2.destroyAllWindows()
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

選択した範囲というのは長方形という認識でよいでしょうか?
以下の手順で指定した範囲だけ背景差分処理を行えます。

 手順

 1. 長方形を左上の点、右下の点で定義する。

xmin, xmax = 200, 600
ymin, ymax = 100, 400 


(xmin, ymin)+-----+
            |     |
            |     |
            +-----+(xmax, ymax)

 2. 長方形でフレームを切り取る。

prev_frame[ymin:ymax, xmin:xmax]
frame[ymin:ymax, xmin:xmax]

 3. 切り取った画像に対して、背景差分に必要な各種処理を行う。

 4. findContours() で輪郭抽出を行う際に offset 引数を渡す。

切り取った画像に対して輪郭抽出しているので、そのままではもとのフレームの座標とは位置がずれてしまう。
offset=(xmin, ymin) と渡してあげると、検出した輪郭点にこの offset を足した値が返り値として返ってくる。

cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE,
                                         offset=(xmin, ymin))

 サンプルコード

サンプルに使用した動画 vtest.avi

import matplotlib.pyplot as plt
import numpy as np
import cv2

# Video Reader を作成
cap = cv2.VideoCapture('vtest.avi')
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)

# Video Writer を作成
fourcc = cv2.VideoWriter_fourcc(*'DIVX')
writer = cv2.VideoWriter('output.avi', fourcc, fps, (width, height))

xmin, xmax = 200, 600
ymin, ymax = 100, 400 

prev_frame = None
while True:
    ret, frame = cap.read()
    if not ret:
        break

    if prev_frame is not None:
        # BGR -> grayscale
        prev_gray = cv2.cvtColor(prev_frame[ymin:ymax, xmin:xmax], cv2.COLOR_BGR2GRAY)
        gray = cv2.cvtColor(frame[ymin:ymax, xmin:xmax], cv2.COLOR_BGR2GRAY)
        # 差分を計算
        diff = cv2.absdiff(gray, prev_gray)
        # 閾値処理
        _, binary = cv2.threshold(diff, 20, 255, cv2.THRESH_BINARY)
        #  輪郭抽出
        _, contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE,
                                         offset=(xmin, ymin))
        # 面積でフィルタリング
        contours = list(filter(lambda cnt: cv2.contourArea(cnt) > 1000, contours))
        # 輪郭を囲む長方形に変換
        rects = [cv2.boundingRect(cnt) for cnt in contours]
        # 長方形を描画する。
        bgr = frame.copy()
        for x, y, width, height in rects:
            cv2.rectangle(bgr, (x, y), (x + width, y + height), (0, 255, 0), 2)
        cv2.rectangle(bgr, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2)

        writer.write(bgr)

    prev_frame = frame

writer.release()
cap.release()
cv2.destroyAllWindows()

イメージ説明

 背景差分について

cv2.absdiff() による前フレームとの単純な差分で背景差分を行うことはおすすめできません。理由は輝度変化などに弱いからです。

OpenCV ではより高度な 背景差分のアルゴリズム が利用できるので、そちらを使ったほうがよいです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/11 06:40 編集

    回答、ありがとうございます。
    サンプルコードまで作成していただき、とても参考になります。
    現在使っている背景差分法のアルゴリズムよりも高度なものがあるとは知りませんでした。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.52%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる