[ภาคหนึ่ง][ภาคสอง]
มาภาคนี้เป็นการปรับปรุง python code และการนำไปทดสอบครับ
วิเคราะห์
งานหลักมีสามงานคือตรวจจับการเคลื่อนไหว จับภาพนิ่งด้วยกล้อง และนำรูปภาพไปวางไว้บน GDriveงานที่อาจจะเป็นคอขวดของระบบก็งานที่สาม เพราะมันโยงกับการสื่อสารผ่านอินเตอร์เน็ต และการ upload ข้อมูล หากเรากำหนดให้มีการทำงานเป็นแบบเป็นไปตามลำดับ (sequential) คงไม่ค่อยดีเท่าไหร่ โชคดีที่ Python มีการกล่าวถึงเรื่องการแยกการทำงานออกเป็น Thread หลักการคือการแยกงานออกเป็นงานย่อย ทำงานอิสระต่อกันไม่ต้องรอกัน แต่งานแต่ละงานอาจใช้ทรัพยากรร่วมกันได้ ในกรณีนี้คือการใช้ Queue [1,2,3] มาเก็บข้อมูลแฟ้มภาพที่กล้องจับมาได้
งานจับภาพนิ่งด้วยกล้อง
งานนี้ผมสร้าง Class ขึ้นมาอีกหนึ่ง Class เพื่อทำงานในลักษณะของ Thread (ท่านสามารถปรับ Class เดิมให้ทำงานเป็น Thread โดยไม่ต้องสร้าง Class ใหม่ก็ได้) โดยการทำงานคือ จับภาพ บันทึกลง sd card แล้วก็ส่งข้อมูลเข้าไปใน queueclass Snapper(threading.Thread):
def __init__(self,queue,capture):
threading.Thread.__init__(self)
self.queue = queue
self.imgcap = ImageCapture()
self.exitFlag=False
self.imgcap=capture
def snap(self,pin):
if self.imgcap and self.queue :
fname = self.imgcap.capture()
if fname :
#fname is null means snapping is error.
#put file name into queueu without waiting time.
self.queue.put(fname,False)
def run(self):
#you need it to make it runnable
while not self.exitFlag :
#do nothing
pass
งาน upload ไฟล์ภาพไปที่ GDrive
ผมก็สร้าง Class มาอีกหนึ่ง ทำงานแบบ Thread โดยจะทำงานก็ต่อเมื่อมีข้อมูลใน queue หรือ queue ไม่ว่าง ทำการดึงข้อมูลออกมาแล้วก็ส่งไป GDrive หลังจากทำงานสำเร็จก็ลบไฟล์ที่อยู่บน SD-Card ทิ้งไหเสีย เพราะไฟล์ไปอยู่บน GDrive แล้วนี่นาclass Uploader(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.gdata = RaspiGData()
self.queue=queue
self.exitFlag=False
def run(self):
import os
while not (self.exitFlag and self.queue.empty()) :
fname = self.queue.get()
if fname :
if not self.gdata.ready :
self.gdata.create_client()
else :
success=self.gdata.upload_image(fname)
if success :
os.unlink(fname)
งานตรวจจับการเคลื่อนไหว
เราใช้ประโยชน์จาก RPi.GPIO [4] ซึ่งทางผู้สร้างเขาได้ให้มันทำงานในลักษณะของ Thread อยู่ ก็เลยสบายไม่ต้องทำอะไรมากนักเพียงแก้ไข Code ของเรานิดหน่อย คือจากเดิมที่เราเคยทำเป็น Class ไว้ก็ไม่ต้องแล้วเพื่อความสะดวกเรามาเรียกใช้งานในตัวโปรแกรมหลักเลย (main.py)pir_pin=7 # PIR Pin is 7th on board GPIO.setmode(GPIO.BOARD) GPIO.setup(pir_pin,GPIO.IN,pull_up_down=GPIO.PUD_UP)
และเราต้องเติมคำสั่งไปอีกสองบรรทัดเพื่อให้ RPi.GPIO ทำงานในลักษณะของ Thread คือ
GPIO.add_event_detect(pir_pin, GPIO.RISING) GPIO.add_event_callback(pir_pin, snapper.snap)
บรรทัดแรกเรากำหนดให้ RPi.GPIO รับฟังเหตุการณ์ที่เกิดขึ้นที่ PIR Pin ของเรา (Pin 7) ด้วยประเภทของเหตุการณ์คือ GPIO.RISING หรือเหตุการณ์ที่ค่าความต่างศักดิ์เริ่มเปลี่ยนจาก 0 ไป 1 เราไม่เอา จาก 1 ไป 0 เพราะเป็นเหตุการณ์ที่วัตถุเคลื่อนที่ผ่าน Sensor ไปแล้ว
บรรทัดที่สอง เราให้ RPi.GPIO ไปเรียกใช้งาน snapper.snap ซึ่งก็คือ function หนึ่งใน Class Snapper ที่สร้างไว้ตอนต้น
แต่งเติม
ผมเติมงานไปสองงานคือ cleanup และ shutdown เพื่อล้างข้อมูลทั้งหมดและ shutdown เพื่อให้ Rasberry Pi ปิดตัวเองหลังการทำงานสมบูรณ์แล้ว จะได้ไม่เปลืองพลังงานdef cleanup(): global imgcap imgcap.quit() GPIO.cleanup() def shutdown(): import subprocess cmd = "/usr/bin/sudo /sbin/shutdown -h now" process = subprocess.Popen(cmd.split(),stdout=subprocess.PIPE) output=process.communicate()[0]
Code ทั้งหมด
main.py
#!/usr/bin/python
##################################
### Author : Somchai Somphadung ###
### Date : 2014-09-20 ###
### N3A Media ###
#################################
from ImageCapture import ImageCapture
from RaspiGdata import RaspiGData
import time
import threading
import Queue
import RPi.GPIO as GPIO
import ConfigParser
import sys
conf_file="gdrive.conf"
#################################################
class Uploader(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.gdata = RaspiGData()
self.queue=queue
self.exitFlag=False
def run(self):
import os
while not (self.exitFlag and self.queue.empty()) :
fname = self.queue.get()
if fname :
if not self.gdata.ready :
self.gdata.create_client()
else :
success=self.gdata.upload_image(fname)
if success :
os.unlink(fname)
pass
#################################################
class Snapper(threading.Thread):
def __init__(self,queue,capture):
threading.Thread.__init__(self)
self.queue = queue
self.imgcap = ImageCapture()
self.exitFlag=False
self.imgcap=capture
def snap(self,pin):
if self.imgcap and self.queue :
fname = self.imgcap.capture()
if fname :
self.queue.put(fname,False)
def run(self):
while not self.exitFlag :
pass
#################################################
def cleanup():
global imgcap
imgcap.quit()
GPIO.cleanup()
def shutdown():
import subprocess
cmd = "/usr/bin/sudo /sbin/shutdown -h now"
process = subprocess.Popen(cmd.split(),stdout=subprocess.PIPE)
output=process.communicate()[0]
#################################################
imgcap = ImageCapture()
imgqueue = Queue.Queue()
if __name__ == "__main__" :
if len(sys.argv) < 2 :
dur = 20
else :
dur = int(sys.argv[1])
config = ConfigParser.ConfigParser()
config.read(conf_file)
pir_pin=int(config.get('gpio','pin'))
if config.get('gpio','mode') == "board" :
GPIO.setmode(GPIO.BOARD)
else :
GPIO.setmode(GPIO.BCM)
GPIO.setup(pir_pin,GPIO.IN,pull_up_down=GPIO.PUD_UP)
uploader = Uploader(imgqueue)
snapper = Snapper(imgqueue,imgcap)
try:
GPIO.add_event_detect(pir_pin, GPIO.RISING)
GPIO.add_event_callback(pir_pin, snapper.snap)
uploader.start()
snapper.start()
s_time=time.time()
e_time=s_time+dur
while s_time < e_time :
s_time=time.time()
print str(e_time - s_time)
time.sleep(1)
except KeyboardInterrupt:
print "Exit"
GPIO.remove_event_detect(pir_pin)
uploader.exitFlag=True
snapper.exitFlag=True
uploader.join()#wait until Thread job is done.
cleanup()
shutdown()
ImageCapture.py
###################################
### Author : Somchai Somphadung ###
### Date : 2014-09-20 ###
### N3A Media ###
##################################
import pygame
import pygame.camera
import pygame.image
import time
import ConfigParser
conf_file="gdrive.conf"
class ImageCapture:
def __init__(self,imgsize=(320,240),pic_format="RGB",loc="/home/pi/"):
# pic_format could be RGB, YUV, HSV
config = ConfigParser.ConfigParser()
config.read(conf_file)
pygame.init()
pygame.camera.init()
cameras = pygame.camera.list_cameras()
if not cameras :
raise ValueError("There is not camera attached")
self.camera=None
w=config.get('captured_image','width')
h=config.get('captured_image','height')
picsize=(int(w),int(h))
picformat=str(config.get('captured_image','format'))
self.camera=pygame.camera.Camera(cameras[0],picsize,picformat)
self.is_end=False
self.img_loc=config.get('captured_image','folder')
def create_img_name(self):
exp="%Y_%m_%d-%H_%M_%S"
return "snap_"+time.strftime(exp)+".jpeg"
def capture(self):
if self.camera is not None :
self.camera.start()
snapshot = self.camera.get_image()
fname = self.img_loc+"/"+self.create_img_name()
pygame.image.save(snapshot,fname)
self.camera.stop()
return fname
else :
return None
def quit(self):
pygame.quit()
RaspiGdata.py
##################################
### Author : Somchai Somphadung ###
### Date : 2014-09-20 ###
### N3A Media ###
#################################
import os.path
import sys
import gdata.data
import gdata.docs.data
import gdata.docs.client
import ConfigParser
conf_file="gdrive.conf"
class RaspiGData :
def __init__(self):
config = ConfigParser.ConfigParser()
config.read(conf_file)
self.ready=False
self.source=config.get('gdrive','source')
self.username=config.get('gmail','user')
self.pwd=config.get('gmail','pwd')
self.folder=config.get('upload_folder','folder')
self.create_client()
def create_client(self):
try:
self.client = gdata.docs.client.DocsClient(source=self.source)
self.client.http_client.debug = False
self.client.ClientLogin(self.username,self.pwd,service=self.client.auth_service, source=self.client.source)
self.ready=True
except :
self.ready=False
def get_folder(self):
col = None
if not self.ready :
return col
for resource in self.client.GetAllResources(uri='/feeds/default/private/full/-/folder'):
if resource.title.text == self.folder :
col = resource
break
return col
def upload(self, file_path, folder_resource):
#Upload document file and return
doc = None
try:
doc = gdata.docs.data.Resource(type='document', title=os.path.basename(file_path))
media = gdata.data.MediaSource()
media.SetFileHandle(file_path, 'image/jpeg')
doc = self.client.CreateResource(doc, media=media, collection=folder_resource)
except:
pass
return doc
def upload_image(self, image_file_path):
folder_resource = self.get_folder()
#if not folder_resource:
# raise Exception('Could not find the %s folder' % self.folder)
if folder_resource:
return self.upload(image_file_path, folder_resource)
else :
return None
นำไปทดสอบ
ผมทำกล่องไม้ด้วยครับ ข้อดีของการไม่มี Case ก็ดีตรงที่เราเอาไปใส่ในภาชนะของเราเองได้ ถ้าต้องการพลางตัวก็ต้องทำตัวเหมือนเครื่องใช้ทั่วไปในบ้าน
![]() |
| รายการอุปกรณ์ |
![]() |
| ยัดลงกล่อง |
![]() |
| พร้อมทดสอบ |
ปล.
เหตุผลว่าทำไมถึงต้องนำภาพไปเก็บไว้ที่ GDrive ทำไมไม่เก็บไว้ที่ SD-Card คำตอบคือเก็บไว้ได้ครับ เพียงแต่ว่าการเก็บภาพไว้ที่อื่นแบบเวลาจริงนั้นจะเป็นการสำรองข้อมูลไปในตัว หลายครั้งที่เกิดเรื่องราวขึ้นเป็นข่าวกันคืออุปกรณ์มักจะถูกทำลายหรือขโมย แล้วเราก็ตามหาอะไรไม่ได้ เพราะข้อมูลภาพมันติดไปกับอุปกรณ์หมดแล้ว การแยกส่วนกันแบบนี้ ถ้าจะเอาภาพไปด้วยก็ต้องไปถามเอาจาก Google โน้นหรือต้องผ่านเราไปก่อนหล่ะ
โปรดตรวจสอบ
โครงงานนี้ใช้ Web Camera ซึ่งในทางปฎิบัติแล้ว ท่านอาจซื้อหามาจากหลากหลายผู้ผลิต แต่ไม่ใช่ทุกรายจะได้รับการทดสอบว่าใช้งานได้กับ Raspberry Pi ดังนั้นก่อนเลือกซื้อ Web Camera มาใช้งานโปรดตรวจสอบข้อมูลกับเว็บแห่งนี้ก่อน http://elinux.org/RPi_USB_Webcams--------------------------------
เอกสารอ้าอิง
[1] http://www.troyfawkes.com/learn-python-multithreading-queues-basics/
[2] https://docs.python.org/2/library/threading.html
[3] http://www.tutorialspoint.com/python/python_multithreading.htm
[4] http://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/






ความคิดเห็น
แสดงความคิดเห็น