物体までの距離とその速度の測定

私が紹介するテクノロジーは、画像内のオブジェクトまでの距離を決定するために見つけた方法で会っていません。 それは普遍的でも複雑でもありません。その本質は、可視フィールド(ビデオカメラを使用していると仮定します)がルーラーで調整され、画像内のオブジェクトの座標がルーラー上のマークと比較されるという事実にあります。 つまり、測定は1本の線または軸に沿って実行されます。 ただし、ピクセルごとにルーラーにマークを保存する必要はありません。キャリブレーションのアルゴリズムは、ピクセルとメートルでのルーラーのサイズと、ルーラーの実際の真ん中のピクセルの座標を知るだけで済みます。 明らかな制限は、平面上でのみ機能することです。

メソッド自体に加えて、この記事ではOpenCVライブラリを使用したPythonでの実装について説明し、video4linux2 APIを使用してLinuxのWebカメラから画像を取得する機能についても説明します。



実際には、道路の直接区間で車までの距離と速度を測定する必要がありました。 長い巻尺を使用し、キャンバスの中央で道路に沿って伸ばし、カメラをセットアップして、巻尺全体がカメラの視野に入り、画像のX軸に合わせられるようにしました。 次のステップは、ルーレットの中央に明るいものを置き、カメラがどこにも動かないように修正し、この中央のピクセル座標を記録することでした。

すべての計算は1つの式にまとめられます。
l = L * K /(W / x-1 + K) 、ここで
lはオブジェクトまでの必要距離、m;
Lは「ライン」の長さ、mです。
W-ピクセル単位の「ルーラー」の長さ。通常は画像の幅と同じです。
xは、画像内のオブジェクトの座標です。
K =(W-M)/ Mはカメラの傾きを反映する係数です。ここで、 Mは「ルーラー」の中央の座標です。

この公式の結論として、三角法の学校知識は私にとって非常に有用でした。

この関数の依存関係グラフを図に示します:


カメラの傾きが大きいほど、グラフが急勾配になります。 境界の場合、カメラの軸が「ルーラー」の平面に垂直に向けられると( M = W / 2 )、グラフは直線になります。

しかし、記事はそこで終わるだけでは短すぎます。 そのため、コンピューターのWebカメラに接続してオブジェクトを監視し、その距離と速度を計算するデモプログラムを作成することにしました。 プログラミング言語として、非常に多くの利点を備えたPythonを選択しました。また、Pythonに付属するTkinterフレームワークを選択してグラフィカルインターフェイスを構築したため、個別にインストールする必要はありません。 OpenCVはオブジェクトの追跡に適しています。バージョン2.2を使用していますが、現在のubuntuバージョン(10.10)のリポジトリにはバージョン2.1のみがあり、APIが少し変更されており、バージョン2.1のプログラムは機能しません。 原則として、グラフィカルインターフェイスと画像キャプチャの機能を割り当てることでOpenCVでプログラム全体を構築することは可能ですが、プログラムの主要部分から分離して、このライブラリを別のものに置き換えたり、追跡をオフにして削除できるようにしたかったのです。 私は古いプログラムを作り直し、不要なものをすべて削除しましたが、驚いたことに、距離と速度の直接計算で残された行はわずかでした。原理的に論理的でしたそして、ウェブカメラの代わりに、RTSP接続のメガピクセルネットワークカメラが使用されます。

ウェブカメラから画像を取得することに関しては、それほど単純ではありません。 Windowsでは、プログラムはDirectXを使用してVideoCaptureライブラリを介してカメラに接続します。ここではすべてが非常に簡単です。 しかし、Linuxでは、PythonのWebカメラの使用に関するわかりやすい記事はほとんどありません。また、通常、いくつかの新しいAPIの変更により動作不能になる例もあります。 以前は、これらの目的でffmpegを使用し、プログラムはCでしたが、ffmpegは少し「銃雀」であり、最終的なプログラムに追加の依存関係を負わせたくありませんでした。 ffmpegも使用するOpenCVを使用できますが、独自のPython用ラッパーvideo4linux2 APIを作成する方法が選択されました。

ソースコードはある学科のページから取られました。 これらのうち、私は目的に不要なものをすべてすぐに削除し、最終的に2つの編集済みファイルV4L2.cppV4L2.h 実際、これはWebカメラに接続するために最低限必要なAPIです。 Pythonのラッパーの作業中に、video4linux2デバイスには、READ、MMAP、およびSTREAMの3つの方法でアクセスできることがわかりましたが、私のWebカメラではMMAPメソッドのみが機能します。 結局のところ、私にとってうまくいかなかったプログラムの他の例では、READメソッドを使用しました。

また、ウェブカメラはYUYV形式(YUV422)の画像を提供し、RGBとは異なり、色情報が2倍少ないことも理解されています。 YUYVでは、2つのピクセルが4バイトでエンコードされ、RGBでは6バイトずつエンコードされるため、節約は1.5倍になります。 Yは、ピクセルごとに異なる輝度成分です。 UとVはピクセルの色を決定する色差コンポーネントです。したがって、2つのピクセルごとにUとVの同じ値を使用します。これらの表記でWebカメラからのバイトストリームを表す場合、YUYV YUYV YUYV YUYV YUYV YUYV-this 12ピクセル。 VLCプレーヤーを使用してWebカメラがどのフォーマットで動作するかを確認し、それを使用してキャプチャデバイスを開き、コーデックに関する情報を要求できます。図のようになります。


Webカメラにアクセスするためのライブラリのソースコードは次のとおりです。
main_v4l2.cpp
 #include "V4L2.h" #include <cstring> #include <iostream> using namespace std; extern "C" { // Specify the video device here V4L2 v4l2("/dev/video0"); unsigned char *rgbFrame; float clamp(float num) { if (num < 0) num = 0; if (num > 255) num = 255; return num; } // Convert between YUV and RGB colorspaces void yuv2rgb(unsigned char y, unsigned char u, unsigned char v, unsigned char &r, unsigned char &g, unsigned char &b) { float C = y - 16; float D = u - 128; float E = v - 128; r = (char)clamp(C + ( 1.402 * E )) ; g = (char)clamp(C - ( 0.344136 * D + 0.714136 * E )) ; b = (char)clamp(C + ( 1.772 * D )) ; } unsigned char *getFrame() { unsigned char *frame = (unsigned char *)v4l2.getFrame(); int i = 0, k = 0; unsigned char Y, U, V, R, G, B; for (i=0;i<640*480*2;i+=4) { Y = frame[i]; U = frame[i+1]; V = frame[i+3]; yuv2rgb(Y, U, V, R, G, B); rgbFrame[k] = R; k++; rgbFrame[k] = G; k++; rgbFrame[k] = B; k++; Y = frame[i+2]; yuv2rgb(Y, U, V, R, G, B); rgbFrame[k] = R; k++; rgbFrame[k] = G; k++; rgbFrame[k] = B; k++; } return rgbFrame; } void stopCapture() { v4l2.freeBuffers(); } // Call this before using the device void openDevice() { // set format struct v4l2_format fmt; CLEAR(fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // Adjust resolution fmt.fmt.pix.width = 640; fmt.fmt.pix.height = 480; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; if (!v4l2.set(fmt)) { fprintf(stderr, "device does not support used settings.\n"); } v4l2.initBuffers(); v4l2.startCapture(); rgbFrame = (unsigned char *)malloc(640*480*3); } } 

アルゴリズムは非常に理解しやすいです-最初に名前が最初に与えられたデバイス(「/ dev / video0」)を開き、次にgetFrameリクエストごとにウェブカメラからフレームを読み取り、RGB形式に変換し、フレームへのリンクをリクエストした人に与えます。 必要に応じて、このライブラリをすばやくコンパイルするためのMakefileも提供しています。

そして、Python用のこのライブラリのラッパーは次のとおりです。
v4l2.py
 from ctypes import * import Image import time lib = cdll.LoadLibrary("linux/libv4l2.so") class VideoDevice(object): def __init__(self): lib.openDevice() lib.getFrame.restype = c_void_p def getImage(self): buf = lib.getFrame() frame = (c_char * (640*480*3)).from_address(buf) img = Image.frombuffer('RGB', (640, 480), frame, 'raw', 'RGB', 0, 1) return img, time.time() 

ご覧のとおり、複雑なことはまったくありません。 ライブラリはctypesモジュールを使用して接続されます。 次の行を除いて、ラッパーの作成に問題はありませんでした。

 frame = (c_char * (640*480*3)).from_address(buf) 

私はすぐには来ませんでした。 実際には、 getFrame()からc_char_pとしてデータを読み取ると、ctypesはデータをゼロで終わる文字列として解釈します。つまり、バイトストリームでゼロが検出されるとすぐに読み取りが停止します。 同じ設計により、読み取るバイト数を明確に指定できます。 私たちの場合、それは常に固定値-640 * 480 * 3です。

ここでは、Windowsで画像を取得するためのソースコードは提供しませんが、複雑さにも違いはなく、 directx.pyという名前のwindowsフォルダーのアーカイブにwindowsます。

そして、OpenCVを使用して記述されたオブジェクトの追跡クラスのソースコードを引用したほうがよいでしょう。 OpenCVに同lkdemo.pyれているlkdemo.py例を例として取り上げ、クラスに変換することでニーズに合わせて再度単純化しました。
tracker.py
 class Tracker(object): "Simple object tracking class" def __init__(self): self.grey = None self.point = None self.WIN_SIZE = 10 def target(self, x, y): "Tell which object to track" # It needs to be an array for the optical flow calculation self.point = [(x, y)] def takeImage(self, img): "Loads and processes next frame" # Convert it to IPL Image frame = cv.CreateImageHeader(img.size, 8, 3) cv.SetData(frame, img.tostring()) if self.grey is None: # create the images we need self.grey = cv.CreateImage (cv.GetSize (frame), 8, 1) self.prev_grey = cv.CreateImage (cv.GetSize (frame), 8, 1) self.pyramid = cv.CreateImage (cv.GetSize (frame), 8, 1) self.prev_pyramid = cv.CreateImage (cv.GetSize (frame), 8, 1) cv.CvtColor (frame, self.grey, cv.CV_BGR2GRAY) if self.point: # calculate the optical flow new_point, status, something = cv.CalcOpticalFlowPyrLK ( self.prev_grey, self.grey, self.prev_pyramid, self.pyramid, self.point, (self.WIN_SIZE, self.WIN_SIZE), 3, (cv.CV_TERMCRIT_ITER|cv.CV_TERMCRIT_EPS, 20, 0.03), 0) # If the point is still alive if status[0]: self.point = new_point else: self.point = None # swapping self.prev_grey, self.grey = self.grey, self.prev_grey self.prev_pyramid, self.pyramid = self.pyramid, self.prev_pyramid 

まず、どのポイントを監視するかを彼に伝えなければなりません。これには、 targetメソッドがあります。 次に、 takeImageメソッドを使用してフレームごとにそれを与え、画像フレームを理解できる形式に変換し、操作に必要な画像アルゴリズムを作成し、フレームをカラーからグレーの濃淡に転送し、これらのすべての関数をCalcOpticalFlowPyrLK送りますルーカス・カナダのピラミッド法。 この関数の終了時に、追跡しているポイントの新しい座標を取得します。 ポイントが失われた場合、 status[0]はゼロになります。 光束は、1点だけでなく計算できます。 ウェブカメラでlkdemo.pyを実行し、多くのポイントをうまく処理していることを確認します。

また、Python Imaging Libraryの画像をOpenCV形式に変換することについても説明します。実際、カラー画像のOpenCVは異なる順序の色成分を使用しますcv.CvtColor(frame, frame, cv.CV_BGR2RGB) 、完全な変換には、コードを行cv.CvtColor(frame, frame, cv.CV_BGR2RGB)ですが、ほとんどのトラッキングアルゴリズムは色成分と混同されているかどうかにかかわらず、まったく同じです。この例では通常、白黒画像のみを使用しています。 したがって、この行をコードに含めることはできません。

また、最も単純な数学しか存在しないため、記事の距離を直接計算するためのクラスのソースコードも提供していません。 ファイルdistance_measure.pyにあります。

グラフィカルインターフェイスを形成し、他のすべてのモジュールをロードするメインスクリプトのソースコードを表示するだけです。
main.py
 from distance_measure import Calculator from webcam import WebCam from tracker import Tracker from Tkinter import * import ImageTk as PILImageTk import time class GUIFramework(Frame): "This is the GUI" def __init__(self,master=None): Frame.__init__(self,master) self.grid(padx=10,pady=10) self.distanceLabel = Label(self, text='Distance =') self.distanceLabel.grid(row=0, column=0) self.speedLabel = Label(self, text='Speed =') self.speedLabel.grid(row=0, column=1) self.imageLabel = None self.cameraImage = None self.webcam = WebCam() # M = 510, L = 0.5, W = 640 self.dist_calculator = Calculator(500, 0.5, 640, 1) self.tracker = Tracker() self.after(100, self.drawImage) def updateMeasure(self, x): (distance, speed) = self.dist_calculator.calculate(x, time.time()) self.distanceLabel.config(text = 'Distance = '+str(distance)) # If you want get km/h instead of m/s just multiply # m/s value by 3.6 #speed *= 3.6 self.speedLabel.config(text = 'Speed = '+str(speed) + ' m/s') def imgClicked(self, event): """ On left mouse button click calculate distance and tell tracker which object to track """ self.updateMeasure(event.x) self.tracker.target(event.x, event.y) def drawImage(self): "Load and display the image" img, timestamp = self.webcam.getImage() # Pass image to tracker self.tracker.takeImage(img) if self.tracker.point: pt = self.tracker.point[0] self.updateMeasure(pt[0]) # Draw rectangle around tracked point img.paste((128, 255, 128), (int(pt[0])-2, int(pt[1])-2, int(pt[0])+2, int(pt[1])+2)) self.cameraImage = PILImageTk.PhotoImage(img) if not self.imageLabel: self.imageLabel = Label(self, image = self.cameraImage) self.imageLabel.bind("<Button-1>", self.imgClicked) self.imageLabel.grid(row=1, column=0, columnspan=2) else: self.imageLabel.config(image = self.cameraImage) # 30 FPS refresh rate self.after(1000/30, self.drawImage) if __name__ == '__main__': guiFrame = GUIFramework() guiFrame.mainloop() 

上で言ったように、グラフィカルインターフェイスを作成するためにTkinterライブラリを選択しました。また、GTK、QT、そしてもちろんwxPythonなどの他のツールキットも使用しましたが、Tkinterはすぐに動作しますが、使いやすいですが、もちろん、その上に複雑なインターフェイスを作成することはできませんが、その機能はタスクに十分すぎるほどです。 クラスの初期化では、 gridを作成して他のウィジェットを配置します:2つのテキストフィールドと1つの画像。 Tkinterを使用すると、ウェブカメラから画像をダウンロードするためのストリームを個別に作成する必要さえありませんでした。一定期間後に指定された機能を実行できるafterメソッドがあるからです。 Labelは、 configメソッドを使用してテキストと画像を更新できます。 とても簡単です! bindメソッドを使用したマウスクリックイベントの処理は、 imgClickedメソッドに変換されます。

画像とそのタイムスタンプはself.webcam.getImage関数self.webcam.getImageself.webcam.getImageます。 Webカメラモジュールは、プログラムが現在実行されているオペレーティングシステムに応じて、Webカメラを操作するための適切なモジュールのみをロードします。

もう一度、プログラム-distance-measureでアーカイブへのリンクを提供します。
ubuntuに必要なパッケージ:python、python-imaging、python-imaging-tk、opencvバージョン2.2およびbuild-essentialはV4L2ラッパーをコンパイルします。
プログラムは以下から開始します。
python main.py
オブジェクトの追跡を開始するには、クリックする必要があります。

それだけです

便利なリンク


Source: https://habr.com/ru/post/J115661/


All Articles