OpenCVで色を抽出して概数をカウント

OpenCVの勉強をしていたら、となりで子供が黄色いボールで遊んでいました。ちょうど良い題材なので、黄色いボールを数える画像認識プログラムを作ります。

黄色のボールだけをカウントする

黄色いボールを取るたびに、画像左下の数字が減っていきます。

赤いミニトマトが減っても数字は変わらず、黄色いボールだけを認識します。

色だけのボール検出は難しい

黄色を抽出するだけなら簡単です。

ボールが重なっていない状態なら、ボールの数を検出することも、比較的簡単です。

しかし、2つのボールが重なっている場合、それを2つのボールと認識させるのは、難易度が高めかもしれません。

機械学習を取り入れることになりそうです。

大ざっぱにでも数えたい

低いコストで、大ざっぱにボールを数えられないでしょうか?

黄色の面積を計算すれば、ボールの概数は出せるかと思います。

元の映像だと、奥に行くほどボールの面積が小さくなるため、まずは射影変換で調整します。

射影変換が下手で、ボールが横に潰れてしまいましたが・・・面積がだいたいそろった気がします。

これで黄色の面積を、1つ当たりのボール面積で割れば、ボールの数をカウントできます。

ボールの数が分かる1フレームを使ってマスクを生成し、cv2.countNonZero(mask) / ボールの数で、1つ当たりボール面積は計算できます。

今回の映像だと、1つのボールの面積は約9,200pxでした。

黄色ボールの概数を数えるコード

色の抽出やマスク生成は前回までのコードを使いました。

射影変換はOpenCV-Pythonチュートリアルを参考にしています。

動作の参考に、冒頭の画像を再掲載します。

今回はボールでしたが、色が識別できれば、形状は何でもOKです。

色だけで概数を簡単に調べたいときのサンプルでした。

“OpenCVで色を抽出して概数をカウント” への2件の返信

  1. 勉強の参考になるなと思い拝見させていただきました
    気になったところがありまして,この下記のコードはどのような処理分からなかったため教えてほしいです.
    mask[((h upper[0])) & (s > lower[1])] = 255
    あと個人的に重なっている部分を作ってみました
    色認識がまだわかっていないため処理はしていません
    #検出する画像の読み込み
    frame = cv2.imread(‘random_clr.png’)#’Inkedcolor_circyle1_LI.jpg’)
    #1チャンネル(白黒画像に変換)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    #Cannyにてエッジ検出処理(やらなくてもよい)
    canny_gray = cv2.Canny(gray,100,200)
    #houghで使う画像の指定、後で変えたりする際に変数してしておくと楽。
    cimg = canny_gray

    j = 0
    #hough関数
    circles = cv2.HoughCircles(cimg,cv2.HOUGH_GRADIENT,1,20,param1=120,param2=20,minRadius=10,maxRadius=30)
    # param1 ; canny()エッジ検出機に渡される2つの閾値のうち、大きいほうの閾値0
    # param2 ; 円の中心を検出する際の投票数の閾値、小さくなるほど、より誤検出が起こる可能性がある。
    # minRadius ; 検出する円の最小値
    # maxRadius ; 検出する円の最大値

    #検出された際に動くようにする。
    if circles is not None and len(circles) > 0:
    #型をfloat32からunit16に変更:整数のタイプになるので、後々トラブルが減る。
    circles = np.uint16(np.around(circles))

    for i in circles[0,:]:
    # 外側の円を描く
    cv2.circle(frame,(i[0],i[1]),i[2],(0,255,0),2)
    # 中心の円を描く
    cv2.circle(frame,(i[0],i[1]),2,(0,0,255),2)
    # 円の数を数える
    j = j + 1
    font = cv2.FONT_HERSHEY_DUPLEX
    #円の合計数を表示
    cv2.putText(frame, “sum{}”.format(j), (5, 30), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 1)
    cv2.putText(frame, “cnt{}”.format(j), (int(i[0]), int(i[1])), font, 0.8, (0,255,255))

    file_name = “sample.txt”

    with open(file_name, encoding=”cp932″) as f:
    data_lines = f.read()

    # 同じファイル名で保存
    with open(file_name, mode=”w”, encoding=”cp932″) as f:
    f.write(format(j))
    #結果画像の表示
    cv2.imshow(”,frame)
    #結果の書き込み
    cv2.imwrite(‘random_clr1.png’,frame)

    1. keiki様。重なっている部分のコードをありがとうございます!
      ご質問いただいた部分の処理については、下記URLの「赤を識別する問題」に記載しています。
      https://temari.co.jp/blog/2017/11/16/opencv-6/

      今回は黄色だったため、質問いただいた部分のコードは使っていません。
      mask = cv2.inRange(hsv, lower, upper)で、色の範囲を指定しています。
      ただ、赤色の場合は、「h(色相)が0~10(橙寄りの赤)または170~180(紫寄りの赤)」というような分断された数字になってしまい、処理できません。
      そのため、lower[0](色相の最小範囲)に負の値を入れた場合は、赤用の別の処理となるようにしました。

      mask[((h < lower[0]*-1) | (h > upper[0])) & (s > lower[1])] = 255

      lower[0]が-10、upper[0]が170なら、色相が10より小さい、または170より大きい場合にマスクをする処理となります。
      また、s(彩度)が高くないと赤に見えないため、最低彩度のlower[1]も条件に入れています。

      ・・・もっと良い方法があるかもしれません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)