Tag Archives: python

OpenCV tracking API with GOTURN

ก็ตามสไตล์ของบล็อคนี้นะครับ ต้องเกริ่นน้ำเยอะหน่อย เข้าเรื่องเลยเดี๋ยวมันจบเร็ว โดยวันนี้เราจะมาพูดถึงการติดตามวัตถุ (Tracking) นอกจากการตรวจจับวัตถุแล้วเนี่ย การติดตามวัตถุก็สำคัญไม่แพ้กันเพราะหากเราต้องการนับวัตถุ หรือตรวจจับว่าเป็นชิ้นเดิมกับที่เราเคยตรวจจับได้รึเปล่านั้นต้องใช้อัลกอริทึมการติดตามวัตถุอย่างแน่นอน จบการเกริ่น อยากเห็นผลลัพธ์เลื่อนไปล่างสุดของโพสได้เลย แต่อ่านหน่อยเถอะอุตส่าห์เขียนมานะ

สำหรับโพสนี้เป็นโพสแชร์ผลลัพธ์ของการใช้งาน Tracking API ของ OpenCV ซึ่งก็มีประสิทธิภาพดี และยังมีให้เลือกทดลองอยู่หลากหลาย Algorithm เช่น MIL KCF GOTURN CSRT Boosting MOSSE MedianFlow TLD ทั้งหมดที่กล่าวมีทั้งข้อดีข้อเสียแตกต่างกันไปตามสถานะการณ์ ลองไปเลือกใช้กันได้นะครับ มีทั้งแบบช้าแบบเร็ว ตัวโค้ดเองเขียนง่ายมาก แต่ถ้าใครจะใช้สำหรับติดตามวัตถุหลายชิ้นพร้อมกันนี่ก็สามารถทำได้เช่นกัน แต่ยังมีข้อจำกัดอีกมาก

แต่วันนี้ที่เลือกมาคือ GOTURN เพราะเป็นอัลกอริทึมเดียวใน Tracking API ที่ใช้ Deep Learning ในการติดตามวัตถุ ซึ่งต้องบอกว่าผลลัพธ์ของการติดตามนั้นขึ้นอยู่กับการเรียนรู้ของโมเดล เราสามารถปรับใช้ได้หลายวัตถุ แต่นั่นคือเราต้องไปสร้างโมเดลขึ้นมาเอง แต่สำหรับอัลกอริทึมอื่นๆใน API นี้จะเป็นการเทียบภาพเป็นหลัก ทำให้อัลกอริทึมส่วนมากเมื่อวัตถุถูกบดบังจะไม่สามารถติดตามต่อได้ เพราะภาพมีความแตกต่างกันมากระหว่างภาพวัตถุแบบเต็มและแบบบดบัง

สำหรับการที่จะใช้ GOTURN ต้องมีการดาวน์โหลดไฟล์โมเดลจาก Reference ด้านล่างโพสนะครับ ส่วนตัว OpenCV ผมใช้ opencv เวอร์ชัน 4.5.2 โดยต้องลงจาก pip install opencv-contrib-python ตัวไลบรารี่นี้มีฟังก์ชันมากกว่าไลบรารี่ opencv-python พอลงไลบรารี่และดาวน์โหลดโมเดล GOTURN มาแล้วก็ไปดูโปรแกรม Python ที่เขียนกันได้เลยครับ

import cv2

cap = cv2.VideoCapture('sea_walk2.mp4')
video_type = cv2.VideoWriter_fourcc('M','P','4','V')
out = cv2.VideoWriter('output.mp4', video_type, 25, (1920,1080))
tracker = cv2.TrackerGOTURN_create()
initial = False

while True:

    (grabbed, frame) = cap.read()

    if not grabbed:
        print('\n End of the video file...')
        break

    if not initial:
        bbox = cv2.selectROI(frame, False)
        initial = True
        tracker.init(frame, bbox)
    else:
        ret, bbox = tracker.update(frame)
        
    p1 = (int(bbox[0]), int(bbox[1]))
    p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
    position = (int(bbox[0] + bbox[2]/2), int(bbox[1] + bbox[3]/2))

    cv2.rectangle(frame, p1, p2, (255,0,0), 4, 1)
    cv2.circle(frame, position, 1, (255,0,0), 5)
    cv2.imshow("Tracking", frame)
    out.write(frame)

    cv2.waitKey(1)

ตัวโปรแกรม Python ด้านบนจะใช้วีดีโอเป็นข้อมูลนำเข้า จากนั้นจะให้ผู้ใช้จำกัดขอบเขตของวัตถุที่จะติดตามจากนั้นก็จะติดตามวัตถุไปจนจบวีดีโอ โดยผลลัพธ์จะออกมาเป็นวีดีโอไฟล์ (output.mp4) ซึ่งหากต้องการเปลี่ยนไปใช้อัลกอริทึมอื่นสามารถปรับบรรทัด cv2.TrackerGOTURN_create() เป็นอย่างอื่นได้หาก OpenCV มีอัลกอริทึมอื่นรองรับ ส่วนด้านล่างนี้เป็นผลลัพธ์ที่เราทำการติดตามตัวบุคคลในวีดีโอ

เป็นไงบ้างครับการติดตามวัตถุโดยใช้ OpenCV Tracking API สามารถทำได้ง่ายขึ้นกว่าแต่ก่อนมาก จากการใช้งานพบว่าของบางชนิดที่ไม่ได้เรียนรู้ไว้ไม่สามารถทำงานได้ดีเทียบกับอัลกอริทึมแบบไม่ใช้ Deep Learning จึงยังมีข้อจำกัดในการใช้งานอยู่มาก ซึ่งผมพยายามสร้างโมเดลใหม่อยู่ครับ ถ้ามีผลอย่างไรจะเอามาแชร์ในครั้งถัดไปนะครับ

มาใช้ Kalman Filter อย่างง่ายกัน

และแล้วก็ได้กลับมาโพสอีกครั้ง คือปีที่แล้วไม่ได้มาเขียนโพสเลยแหละ ถ้าถามว่างานเยอะเหรอ เปล่าเลยครับ ขี้เกียจเป็นหลัก พอเริ่มก็เป็นเรื่องยากเลย แต่ไม่ต้องกลัวครับ ตรงไหนยากผมก็จะข้ามไปเพราะก็ยังไม่เข้าใจเช่นกัน แต่โพสนี้จะเน้นประยุกต์ใช้ก่อนละกัน คิดว่าน่าจะเป็นความรู้ให้ใครหลายคนได้และรวมตัวผมเองในอนาคตด้วย

มาเริ่มกันเลยดีกว่าเดี๋ยวจะเบื่อกันเสียก่อน Kalman Filter คือฟิลเตอร์กรองข้อมูลชนิดหนึ่งซึ่งใช้กับข้อมูลที่มีสัญญาณรบกวนสูง และเหมาะสำหรับการติดตามวัตถุ (tracking) เพราะในการสร้างโมเดล Kalman สามารถนำความเร็วของวัตถุมาคำนวณด้วยได้ แต่เดี๋ยวครั้งหน้าจะมาทำเรื่องการติดตามวัตถุให้ดู แต่ครั้งนี้ขอเป็นการฟิลเตอร์ข้อมูลแบบงอย่างง่าย ไปก่อนละกันนะครับ

Kalman ประกอบไปด้วย 2 ส่วนใหญ่คือ 1.การทำนาย (predict) ซึ่งคือการทำนายค่าของข้อมูลที่ควรจะเป็นจากสถานะที่จดจำไว้ เช่น ข้อมูลเก่าและความเร็ว เป็นต้น และ 2.การแก้ไขสถานะ (update) หรือถ้าเรียกตามฟังก์ชันคือ correct นั่นคือเมื่อมีข้อมูลวัดมาได้ใหม่จะทำการนำไปเทียบกับค่าที่ทำนายได้ เพื่อปรับปรุงสถานะภายในโมเดล อันที่จริงแล้วโมเดล Kalman สามารถให้ค่า Covariance ของข้อมูลได้ แต่เข้าใจยากไปหน่อย เอาเป็นว่าในโพสนี้ขออธิบายส่วนที่จะประยุกต์ใช้แบบง่ายก่อน เพื่อไม่ให้หลงคิดว่ายากมาดูโค้ดเลยดีกว่า

kalman = cv2.KalmanFilter(2,1,0)

kalman.statePost = np.array([[data[0]],[0]])
kalman.errorCovPost = 1. * np.ones((2, 2))
kalman.transitionMatrix = np.array([[1., 1.], [0., 1.]])
kalman.measurementMatrix = 1. * np.ones((1, 2))
kalman.processNoiseCov = 1e-7 * np.eye(2)
kalman.measurementNoiseCov = 1e-2 * np.ones((1, 1))

for datum in data:
    prediction = kalman.predict()
    print(prediction[0])

    measurement = np.array([datum])
    kalman.correct(measurement)

จากโค้ดจะเห็นได้ว่าส่วนใช้งานนั้นไม่ได้เยอะอย่างที่คิด แต่ส่วนเริ่มต้นเซ็ทอัพนี่เยอะใช่ย่อย ซึ่งโค้ดเบื้องต้นมีการ import numpy และ data ไว้อยู่แล้วนะ สำหรับโค้ดนี้เป็นข้อมูลแบบ 1 มิติ ดังนั้นจะกำหนดให้มีสถานะ 2 อย่าง คือ ค่าของข้อมูลและความเร็วของการเปลี่ยนแปลงค่า เห็นได้จากบรรทัดที่ 3 (statePost) จากนั้นในบรรทัดที่ 5 คือเมทริกที่อธิบายถึงการเปลี่ยนสถานะเก่ามาเป็นสถานะใหม่ ซึ่งจะมีความคล้ายคลึงกับ measurementMatrix ถ้าถามว่าต่างกันอย่างไรอธิบายมากเดี๋ยวจะงงลองไปดูลิงก์นี้ดีกว่า transitionMatrix คือ F และ measurementMatrix คือ H ถ้างงยังไงทิ้งคอมเม้นไว้ได้ครับ ถ้าตอบได้ผมจะมาตอบนะ

แล้วก็มาถึงส่วนสำคัญที่สุดที่ทุกท่านจำเป็นจะต้องปรับจูนเองเพื่อให้ได้ค่าที่ต้องการ คือ processNoiseCov และ measurementNoiseCov (ซึ่งคือ w กับ v ตามลำดับตามลิงก์ในย่อหน้าก่อน) ถามว่ามีหลักการปรับยังไงเอาเป็นว่าปรับจนได้กราฟที่ต้องการเลยแล้วกันครับ ยกตัวอย่างข้างล่างคือใช้ measurementNoiseCov เป็น 0.1 และ 0.01

Noise 0.1

Measurement Noise 0.1


Noise 0.01

Measurement Noise 0.01

แต่จะเห็นได้ว่าช่วงที่ข้อมูลมีการเปลี่ยนแปลงกระทันหันโมเดล Kalman จะวิ่งเลยข้อมูลไปแล้วกลับเข้ามาใหม่ เหตุผลเพราะว่าโมเดลยังคงรักษาความเร็วอยู่ไม่สามารถเปลี่ยนความเร็วได้กระทันหัน ดังนั้นฟิลเตอร์นี้จึงเหมาะกับการติดตามวัตถุเป็นอย่างมาก อย่างไรก็ตามแม้ข้อมูลของผมจะมีสัญญาณรบกวนมากแต่ฟิลเตอร์นี้ก็ยังไม่ผันแปรตามสัญญาณรบกวน ซึ่งก็ถือได้ว่าทำงานได้ดีและตัวโค้ดไม่เยอะมากด้วย เดี๋ยวคราวหน้าจะมาทำฟิลเตอร์อีกแบบหนึ่งที่ไม่คิดความเร็ว แล้วลู่เข้าสู่ข้อมูลเร็วกว่า ไว้พบกันคราวหน้านะครับ

Graphviz with Python

นี่ก็สิ้นเดือนอีกแล้วนะครับ ยังไม่ได้ไปหาเนื้อหาใหม่มาเขียนเลย แต่เนื่องจากตั้งใจไว้ว่าทุกเดือนต้องเขียนอย่างน้อยหนึ่งบทความ วันนี้เลยจะมาเสนอ Tool สำหรับวาดกราฟบน Python สำหรับคนที่จะนำไปทำเขียนเปเปอร์หรืออยากให้โปรแกรมแสดงผลเป็นกราฟนะครับ

Tool ที่จะมานำเสนอชื่อ Graphviz ครับซึ่งสามารถติดตั้งได้โดยคำสั่งข้างล่างนี้ (สำหรับระบบปฎิบัติการ Debian นะครับ)

$ sudo apt-get install graphviz
$ sudo pip install graphviz

สำหรับการใช้งานนะครับ ก็ทำการ import ในโค้ด Python ของเราได้เลย นี่คือตัวอย่างโค้ด

import graphviz as gv

graph = gv.Digraph(format="jpg")
graph.attr('graph',{'rankdir':'LR','splines':'ortho'})

layer = [7,6,5,3,3]
layer_name = ['input', 'L1 AE', 'L2 AE', 'L3 AE', 'output']

# init node
layers = []
for layer_id in range(len(layer)):
        layers.append([])
        subgraph = gv.Digraph(name="cluster%d" % (layer_id))
        subgraph.attr('graph', {'label':layer_name[layer_id]})
        
        for node_id in range(layer[layer_id]):
                node_name = 'L_%d_%d' % (layer_id, node_id)
                layers[layer_id].append(node_name)
                subgraph.node(node_name, label='')
        graph.subgraph(subgraph)

# init full-connected edges
for layer_id in range(len(layer)-2):
        for node_source in layers[layer_id]:
               for node_sink in layers[layer_id+1]:
                        graph.edge(node_source, node_sink)

# final layer
for node_id in range(3):
        graph.edge('L_3_%d' % (node_id), 'L_4_%d' % (node_id))

filename = graph.render(filename='img/neural_net')
print filename

และนี่คือตัวอย่างของภาพที่ได้จากการรันโค้ดข้างบนครับ

Neural Network Structure

Neural Network Structure

การทำงานของโปรแกรมจะทำงานโดยสร้างไฟล์ชนิด dot ขึ้นมาจากนั้นแปลงไฟล์ dot ให้เป็นไฟล์ภาพ เพราะฉะนั้นถ้าอยากได้กราฟที่สวยหรูกว่านี้ต้องลองดู property ต่างๆ จาก link นี้ได้เลยครับ