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



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