OpenCVでパルス数をカウントする


ラズパイやICの出力するパルス数をオシロスコープでカウントすることがありますが、数個ならともかく数が多くなってくると目視では厳しくなります。

一番簡単なのは、立ち上がりエッジのコーナーの部分でテンプレートマッチングを行うことで、マッチした数がパルス数になります。

に対して、テンプレートのマッチングをすると、

import cv2 as cv
import numpy as np

img = cv.imread("pulse.png")
img_gray = cv.imread("pulse.png", 0)
template = cv.imread("template.png", 0)

w,h = template.shape[::-1]
res = cv.matchTemplate(img_gray, template, cv.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where(res >= threshold)
print(f"pulse count {len(loc[0])}")

for pt in zip(*loc[::-1]):
    cv.rectangle(img, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
cv.imshow("", img)
cv.waitKey(0)
cv.destroyAllWindows()

実行結果

pulse count 12

ルーラーが交差するところもヒットしてしまいましたが、これはルーラーを非表示にすれば済む話です。実際には8個なのにカウントが12になっているのは、ひとつのエッジを2回カウントしている箇所があるためです。

テンプレートマッチングは、対象画像上でテンプレート画像をスライドさせて、マッチングする(相互相関の値が大きい)箇所を抽出する方法です。今回はシンプルなテンプレートなので、ピークから1ピクセルスライドしただけでは相互相関が十分に下がらない(マッチングしてしまう)結果になっています。
実際、先頭のエッジの座標(50,222)周辺の相互相関は、以下のようになっていて

(49,222) 0.63255876
(50,222) 0.9331638
(51,222) 0.83463985
(52,222) 0.55312735

ひとつのエッジを2回カウントしていることがわかります。
近くのマッチングを除外するようにすると、

count = 0
prev = (0,0)
for pt in zip(*loc[::-1]):
    if pt[0] - prev[0] > w or pt[1] - prev[1] > h:
        cv.rectangle(img, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
        count += 1
    prev = pt
print(f"pulse count {count}")

実行結果

pulse count 8

パルス数をカウントすることができました。

ちなみに、マッチングの手法は複数ありますが、今回は正規化相互相関(TM_CCOEFF_NORMED)を使っています。どれを使うかは、好みの部分が大きいと思います。

今回のテンプレートマッチングは、リプルを含むエッジを検出できないなど、汎用性に欠けます。その都度テンプレートを作成すればよいのですが、もう少しよい方法があると思います。