[ภาคหนึ่ง][ภาคสอง]
มาภาคนี้เป็นการปรับปรุง 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/
ความคิดเห็น
แสดงความคิดเห็น