สไลด์ประกอบกิจกรรม one (language) for all (platforms)
TRANSCRIPT
One (language) for All (platforms)development note
ผศ.ดร.ศ�ภช�ย วรพจน�พ�ศ�ทธ��
ภาคว�ชาว�ศวกรรมไฟฟ้าและคอมพ�วเตอร�
มหาว�ทยาล�ยธรรมศาสตร�
บ�นทกึ
● 13 ม%ค. 58การต�ดต�ง้ Python และเครือ่งมอืการต�ดตอ่เว็บบร�การ Google Mapsการต�ดต�ง้ Raspberry Pi
● 19 ม%ค. 58การต�ดต�ง้ OpenCV สำาหร�บ Windows + Raspberry Piการเข%ยนโคด้ computer vision
● 26 ม%ค. 58การต�ดต�ง้ Python SDK สำาหร�บ Google App Engineการพ�ฒนา web app และ web service
เตร%ยม Python และ IDE● ดาวน�โหลดและต�ดต�ง้ Python 2.7.8
http://www.python.org● ดาวน�โหลดและต�ดต�ง้ PyCharm IDE
https://www.jetbrains.com/pycharm/
ทดสอบ Python environment● เป�ด PyCharm แลว้เลอืกสรา้ง Pure Python project
ตรวจสอบวา่ Interpreter ตรงก�บร�น่ท%ต่ �ดต�ง้
● คล�กท% ่Python console แลว้พ�มพ� print('hello') เพือ่ยนืย�นการทำางาน
ทดสอบ Python IDE● คล�กขวาท%ช่ ือ่ project แลว้เลอืก New → Python file● พ�มพ�โคด้เพือ่ทดสอบ
if __name__ == '__main__': print('hello, world')
● เลอืกเมนู Run เพือ่ส�ง่ทำางาน เชค็ผลจากหนา้ตา่ง Run
อา่นเข%ยนไฟล�
● เตร%ยมไฟล� test.txt ท%เ่ป็นขอ้ความสำาหร�บใชท้ดสอบ ● พ�มพ�โคด้สำาหร�บน�บคำาจากไฟล� แลว้ส�ง่ Run
if __name__ == '__main__': f = open('test.txt', 'r') counts = {} content = f.read() for word in content.split(): if word not in counts: counts[word] = 1 else: counts[word] += 1 for k,v in counts.items(): print k,v
เขา้ถงึเนือ้หาบนอ�นเตอร�เน็ต
● ขอ้มลูอส�งหาร�มทร�พย�ท% ่Sacramento เป็น CSVhttp://samplecsvs.s3.amazonaws.com/Sacramentorealestatetransactions.csv
● พ�มพ�โคด้สำาหร�บการอา่นไฟล�จากเว็บ แลว้ส�ง่ Run
import urllibif __name__ == '__main__': url = 'http://…csv' conn = urllib.urlopen(url) content = conn.read() conn.close() lines = content.split('\r') table = [] for line in lines: fields = line.split(',') if not table: for field in fields: table.append({'name':field, 'column':[]}) else: idx = 0 for field in fields: table[idx]['column'].append(field) idx += 1 for item in table: print(item['name'])
ดงึไฟล�จากเว็บไซต�
แยกเนือ้หาตามบรรท�ด
แยกขอ้มลูดว้ย ,
ประกอบขอ้มลูใหม่
ทดสอบใชด้%บ�๊กเกอร�
● คล�กต�ง้ breakpoint ท%ด่า้นหนา้โคด้
lines = content.split('\r')
● เลอืกเมนู Run → Debug● ตรวจสอบต�วแปรในหนา้ตา่งยอ่ย Variables● ใชป้�่ มบน toolbar เพือ่ส�ง่ทำางาน
ใชง้านบร�การ Google Maps● API สำาหร�บเขา้ใชเ้ว็บบร�การของ Google Maps
https://developers.google.com/maps/documentation/webservices/
● ทดสอบบร�การ Geocoding ดว้ยเบราเซอร�
https://maps.googleapis.com/maps/api/geocode/json?address=Bangkok+Thailand
● พ�มพ�คำาส�ง่ลงใน Python console เพือ่ทดลองการแยกขอ้มลูในรปูแบบ jsonimport urllibimport json
url = 'https://maps.googleapis.com/maps/api/geocode/json?'args = {'address':'Bangkok,Thailand','language':'th'}url += urllib.urlencode(args)conn = urllib.urlopen(url)resp = json.load(conn)
● ทดลองดงึคา่ออกจากผลล�พธ�ของเว็บบร�การresp.keys()print resp['status']type(resp['results'])
ทดสอบการแยกขอ้มลู
ไดผ้ลล�พธ� [u'status', u'results']
ลำาด�บการดงึขอ้มลูพ�ก�ด
● ลำาด�บในการแยกขอ้มลูพ�ก�ด lat/lng ของสถานท%่type(resp['results'][0])<type 'dict'>resp['results'][0].keys()[u'geometry', u'address_components', u'place_id', …type(resp['results'][0]['geometry'])<type 'dict'>resp['results'][0]['geometry'].keys()[u'location', u'bounds', u'viewport', …type(resp['results'][0]['geometry']['location'])<type 'dict'>resp['results'][0]['geometry']['location'].keys()[u'lat', u'lng']resp['results'][0]['geometry']['location']{u'lat': 13.7563309, u'lng': 100.5017651}
ขอ้มลูแบบ list
ผลล�พธ�
ดงึขอ้มลูพ�ก�ดจากสถานท%่
● พ�มพ�โคด้ดงึขอ้มลู แลว้ส�ง่ Runimport urllibimport json
if __name__ == '__main__': url = 'https://.../maps/api/geocode/json?' args = {'address':'Bangkok,Thailand','language':'th'} url += urllib.urlencode(args) conn = urllib.urlopen(url) resp = json.load(conn) latlng = resp['results'][0]['geometry']['location'] print latlng['lat'],latlng['lng'] for place in resp['results']: print place['formatted_address']
เตร%ยม Raspberry Pi
ดาวน�โหลดและต�ดต�ง้ Raspbian ลงใน SD card โดยใช ้โปรแกรม Win32 Disk Imager
● http://downloads.raspberrypi.org/raspbian_latest● http://sourceforge.net/projects/win32diskimager/
เตร%ยมคอมพ�วเตอร� (Windows 7)● ต�ง้คา่ Network and Connection Sharing โดยเลอืก
รายการ change adapter settings● เลอืกแชร� WiFi → LAN (Ethernet)
เชือ่มตอ่ PC ↔ Raspberry Pi● เส%ยบ SD card เขา้ Raspberry Pi● เส%ยบสาย LAN และ USB เขา้คอมพ�วเตอร�
เชค็สถานะบตู/เครอืขา่ยจาก LED ส%เข%ยว
● พ�มพ�คำาส�ง่ cmd ใน Start menu ของ Windows● พ�มพ�คำาส�ง่ arp -a เพือ่ด ูIP address ของบอร�ด
ล็อกอ�นผา่น Ethernet● ดาวน�โหลดโปรแกรม PuTTY
http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
● ล็อกอ�นผา่น ssh โดยป้อน IP address ของบอร�ด● รห�สผูใ้ช ้pi และรห�สผา่น raspberry
สง่ไฟล�จาก PC● ดาวน�โหลดและต�ดต�ง้โปรแกรม WinSCP (secure copy)
http://sourceforge.net/projects/winscp/● โอนยา้ยไฟล� ???.py ไป แลว้ทดสอบเร%ยกใช ้เชน่
python test.py
ต�ง้คา่เร�ม่ตน้ใหบ้อร�ด
● พ�มพ�คำาส�ง่ sudo raspi-config● เลอืกรายการ Expand Filesystem เพือ่ขยายระบบไฟล�
ใหใ้ชเ้ต็มพืน้ท% ่SD card● กดป�่ มลกูศรหรอื Tab เพือ่เลอืก <Finish>
ต�ดต�ง้ซอฟต�แวร�พืน้ฐาน
● พ�มพ�คำาส�ง่เพือ่ต�ดต�ง้แพคเกจ
sudo apt-get install tightvncserver
● เป�ดใชง้าน vncserver แลว้ป้อนรห�สผา่น
vncserver :0 -geometry 800x600 -depth 16
● ไปท%เ่ว็บ chrome เพือ่ต�ดต�ง้แอพ VNC viewerhttps://chrome.google.com/webstore/category/apps
ต�ดต�ง้ VNC client บน PC● เชือ่มตอ่บร�การ VNC โดยป้อน IP address:0● เลอืก LXTerminal เพือ่เป�ด console ร�บคำาส�ง่
ต�ดต�ง้เครือ่งมอืท%จ่ำาเป็น
● การต�ง้ vncserver ใหท้ำางานชว่งบตูระบบ
http://www.raspberrypi.org/documentation/remote-access/vnc/
● แกไ้ขการทำางานของสคร�ปต� vncboot ใหเ้ป็นผูใ้ช ้pisudo -u pi vncserver :0 -geometry 800x600 -depth 16 -pixelformat 565
ต�ดต�ง้เครือ่งมอืสำาหร�บ Python● พ�มพ�คำาส�ง่เพือ่ต�ดต�ง้ wxPythonsudo apt-get install python-wxgtk2.8 wx2.8-i18n python-wxtools
● เลอืกแอพ PyCrust จากเมนู
ป�่ ม Ctrl + ] หรอื [ เพือ่ขยาย/ลดขนาด font
● พ�มพ�คำาส�ง่ pyalamode หรอื pyalacarte เพือ่เป�ดใช ้editor
ต�ดต�ง้ OpenCV ใน PC● ดาวน�โหลดและต�ดต�ง้ numpy สำาหร�บ Python 2.7
http://sourceforge.net/projects/numpy/files/NumPy/● ดาวน�โหลดและขยายไฟล� OpenCV
http://sourceforge.net/projects/opencvlibrary/files/ opencv-win/
● สำาเนาไฟล� cv2.pyd จากโฟลเดอร� opencv\build\ python\2.7\x86 ไปท% ่C:\Python27\Lib
● ทดสอบการต�ดต�ง้จาก Python Consoleimport cv2
สรา้งโครงของ computer vision● พ�มพ�โคด้เพือ่อา่นภาพจากเว็บแคม กล�บดา้น และแสดง
ผลทางหนา้ตา่งimport cv2if __name__ == '__main__': cap = cv2.VideoCapture(0) cv2.namedWindow('mirror') while True: res,frame = cap.read() cv2.flip(frame, 1, frame) cv2.imshow('mirror', frame) if cv2.waitKey(100) == 27: break cap.release() cv2.destroyAllWindows()
รอ 100 msecป�่ ม Esc เพือ่หย�ด
อา่นภาพประมวลผลนำาไปใช ้
ต�ดต�ง้ OpenCV ใน RPi● พ�มพ�คำาส�ง่เพือ่ต�ดต�ง้ OpenCV
sudo apt-get install python-opencv● เร%ยกใช ้pyalamode เพือ่พ�มพ�โคด้ mirror.py● พ�มพ�คำาส�ง่เพือ่เร%ยกใช ้
python mirror.py
การจ�บภาพจากเว็บแคมดว้ย OpenCVใน Raspberry Pi จะหน่วง และม%ความซำ้าของภาพ
ใช ้filter เพือ่แปลงรปู
● เข%ยนโคด้แทรกใน mirror.py● สรา้งฟ�งก�ช�นกอ่น if __name__ == '__main__':
def onChange(pos): print 'Current position is ' + str(pos)
● สรา้ง trackbar หล�งบรรท�ด cv2.namedWindow()
cv2.createTrackbar('filter', 'mirror', 0, 2, onChange)● เข%ยนโคด้เพือ่ทำาเงือ่นไขประมวลผลภาพ
0 ไมป่ระมวลผล1 แปลงเป็นภาพเฉดเทา2 แปลงเป็นเสน้ขอบ (อ�ลกอร�ธมึ Canny)
ใช ้filter เพือ่แปลงรปู
● เข%ยนโคด้แทรกหล�งบรรท�ด cv2.flip()if cv2.getTrackbarPos('filter', 'mirror') == 1: frame = cv2.cvtColor(frame, cv2.cv.CV_BGR2GRAY)
if cv2.getTrackbarPos('filter', 'mirror') == 2: frame = cv2.cvtColor(frame, cv2.cv.CV_BGR2GRAY) frame = cv2.Canny(frame, 50, 200)
สรา้ง time lapse camera● เข%ยนโคด้เพือ่สรา้งต�วบ�นทกึ video โดยกำาหนด format
ก�บ frame ratew = int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))h = int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))format = cv2.cv.CV_FOURCC('M','J','P','G')out = cv2.VideoWriter('test.avi', format, 10, (w,h))
● บ�นทกึเฟรมลงในคล�ปout.write(frame)
● ควบค�ม frame rate โดยการหน่วงเวลาif cv2.waitKey(100) == 27:
import cv2
if __name__ == '__main__': cap = cv2.VideoCapture(0) w = int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) h = int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)) format = cv2.cv.CV_FOURCC('M','J','P','G') out = cv2.VideoWriter('test.avi', format, 10, (w,h)) cv2.namedWindow('preview') while True: res,frame = cap.read() frame = cv2.flip(frame, 1) cv2.imshow('preview', frame) out.write(frame) if cv2.waitKey(100) == 27: break cap.release() cv2.destroyAllWindows()
ใน Raspberry Pi ใหต้�ดต�ง้ VLC player เพือ่ดไูฟล�ท%บ่�นทกึsudo apt-get install vlc
ต�ง้สือ่บ�นทกึ
หาตำาแหน่งแลว้แปลงพ�ก�ด
● ประย�กต�ใชใ้นแอพประเภทสแกนเอกสาร
http://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/
● cv2.cvtColor()● cv2.Canny()● cv2.findContours()● cv2.warpPerspective()
แปลงภาพเป็นเฉดเทา
หาขอบดว้ยCanny
Boundingrectangle
Perspectivewarping
แปลงภาพเป็น binaryret,frame = cap.read()(h,w,_) = frame.shape; w = w/2; h = h/2img = cv2.resize(frame, (w,h))img = cv2.flip(img, 1)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)gray_img = cv2.GaussianBlur(gray_img, (5, 5), 0)edge_img = cv2.Canny(gray_img, 50, 100)
kernel = numpy.ones((5,5), numpy.uint8)edge_img = cv2.morphologyEx(edge_img, cv2.MORPH_CLOSE, kernel)
อา่นและยอ่ภาพ
หาขอบ
หา 4 จ�ดลอ้มภาพ
(cnts, _) = cv2.findContours(edge_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:5]
rect = numpy.zeros((4, 2), dtype=numpy.float32)for c in cnts: peri = cv2.arcLength(c, True) pts = cv2.approxPolyDP(c, 0.01 * peri, True) if len(pts) == 4 and cv2.contourArea(c) > 0.15*w*h: cv2.drawContours(img, [pts], -1, (0, 255, 0), 2)
sums = [pt[0].sum() for pt in pts] diffs = [numpy.diff(pt[0]) for pt in pts] rect[0] = pts[numpy.argmin(sums)][0] rect[1] = pts[numpy.argmin(diffs)][0] rect[2] = pts[numpy.argmax(diffs)][0] rect[3] = pts[numpy.argmax(sums)][0]
ลอ้มเสน้
หาจ�ดท% ่4 ม�ม
สรา้งกรอบ
แปลงพ�ก�ดของภาพ
(tl,tr,bl,br) = recttop = cv2.norm(tr,tl)bottom = cv2.norm(br,bl)width = max(int(top), int(bottom))left = cv2.norm(tl,bl)right = cv2.norm(tr,br)height = max(int(left), int(right))dst = numpy.array([[0, 0],[width - 1, 0], [0, height - 1],[width - 1, height – 1]], dtype=numpy.float32)
M = cv2.getPerspectiveTransform(rect, dst)warped = cv2.warpPerspective(img, M, (width, height))warped = cv2.flip(warped,1)
คำานวณขนาด
แปลงพ�ก�ด
แนวค�ด Internet of Things
Cloud computing
gateway
sensor
คา่เซน็เซอร�
serverHTTP - REST
เตร%ยม Google App Engine● เงือ่นไข: ม% google account + ฟร% = จำาก�ดการใช ้● ดาวน�โหลดและต�ดต�ง้ App Engine SDK for Python
https://cloud.google.com/appengine/downloads
ข�น้ตอนสรา้งบร�การบน cloud● กำาหนดชอ่งทาง (URL) ในการเขา้ถงึบร�การ = web API
http://AAA.appspot.com/xxx/yyy/zzz?arg1=?&arg2=?
ต�วอยา่งเชน่ บร�การ Geocoding ของ Google Mapshttps://maps.googleapis.com/maps/api/geocode/ json?address=Bangkok+Thailand
● ออกแบบโมเดลของการแลกเปล%ย่นขอ้มลู● เข%ยนโคด้ประมวลผล HTTP requests: GET,POST,...
จำาแนกขอ้มลู > จ�ดการฐานขอ้มลู > สรา้งผลล�พธ�● ตอบสนอง = return code + HTML (web apps) หรอื
XML/JSON (web services)
กรณ%ต�วอยา่ง sensor acquisition● ม�มมองอ�ปกรณ� = data entity
คา่ + ค�ณสมบ�ต� (what,which,where,how,...)● ม�มมองเซ�ร�ฟเวอร� = database entity
เวลา + อา้งอ�ง + คา่ + ค�ณสมบ�ต� (what,how,...)● ม�มมองผูใ้ช ้= information
เทรนด�ของคา่ / ต�วแทนของคา่ขอ้มลู / ...
application: rpi-boxversion: 1runtime: python27api_version: 1threadsafe: yes
handlers:- url: /favicon\.ico static_files: favicon.ico upload: favicon\.ico
- url: /ws script: service.app- url: /ui script: ui.app- url: .* script: main.app
libraries:- name: webapp2 version: latest- name: jinja2 version: latest
ไลบราร%ท%ข่อใช ้
การเชือ่ม URL ก�บโคด้
ชือ่ของแอพพล�เคช�น
ไฟล� app.yaml
การใชง้าน Google App Engine● กดป�่ ม Run เพือ่ใช ้PC เป็นเซ�ร�ฟเวอร�● ใชเ้บราเซอร�ดกูารทำางานจากพอร�ต
http://localhost:port● กดป�่ ม Logs เพือ่ตรวจสอบสถานะทำางาน● ตรวจสอบฐานขอ้มลู
http://localhost:admin_port● หล�งยนืย�นการทำางาน กดป�่ ม Deploy เพือ่อ�พโหลด● ใชเ้บราเซอร�ไปท%เ่ซ �ร�ฟเวอร�
http://appname.appspot.com
โครงของเว็บบร�การ (service.py)● HTTP request > ประมวลผล > JSON
#!/usr/bin/env pythonimport webapp2import jsonclass TestHandler(webapp2.RequestHandler): def get(self): resp = {'status':'OK'} self.response.write(json.dumps(resp))app = webapp2.WSGIApplication([ ('/ws/test', TestHandler)], debug=True)
ใชเ้บราเซอร�ดทู% ่http://localhost:xxxx/ws/test
เตร%ยมตน้แบบขอ้มลู (model.py)● น�ยามขอ้มลู
when = เวลา which = แหลง่ขอ้มลูwhat = ขอ้มลู how = ว�ธ%การwhere = ตำาแหน่ง ...from google.appengine.ext import ndb
class MyData(ndb.Model): timestamp = ndb.DateTimeProperty(auto_now_add=True) serial = ndb.IntegerProperty(required=True) value = ndb.FloatProperty(required=True)
เพ�ม่บร�การ (service.py)class UpdateHandler(webapp2.RequestHandler): def get(self): sn = self.request.get('sn') val = self.request.get('value') if sn != '' and val != '': resp = {'status':'OK'} data = MyData(serial=int(sn),value=float(val)) data.put() else: resp = {'status':'ERR'} self.response.write(json.dumps(resp))app = webapp2.WSGIApplication([ ('/ws/test', TestHandler), ('/ws/update', UpdateHandler)], debug=True)
แยกขอ้มลูจาก arg
จ�ดเก็บขอ้มลู
http://appname.appspot.com/ws/update?sn=11&val=1.0
เตร%ยมเชือ่มตอ่ Arduino● Raspberry Pi สามารถเชือ่มตอ่ Arduino ผา่นพอร�ต
USB ท�ง้รปูแบบขอ้มลูและโคด้
sudo apt-get install arduino arduino-mk python-serial
● เส%ยบ Arduino เขา้ก�บ Raspberry Pi● พ�มพ�คำาส�ง่ dmesg ดรูายงานการเชือ่มตอ่● ตรวจสอบพอร�ตอน�กรมท%เ่ป็น Arduino: ttyACMx หรอื
ttyUSBx> ls /dev/tty*
สรา้งแหลง่ขอ้มลู
● เข%ยนโคด้บน Arduino เพือ่ส�ม่ว�ดและรายงานผลvoid setup() { Serial.begin(9600);}void loop() { int val = analogRead(0); Serial.println(val); delay(1000);}
หากม%หลายขอ้มลู แนะนำาใหใ้ช ้csv หรอื jsonSerial.print(val1); Serial.print(',');Serial.print(val2); Serial.print(',');Serial.println(val3);
สง่ผา่นขอ้มลู
● สรา้งไฟล� data_feed.py สำาหร�บ Raspberry Piimport jsonimport serialimport urllibif __name__ == '__main__': ser = serial.Serial('/dev/ttyACM0',9600,timeout=10) ser.flush() while True: line = ser.readline() data = line.strip() args = {'sn':11, 'value':data} url = 'http://rpi-box.appspot.com/ws/update?' conn = urllib.urlopen(url + urllib.urlencode(args)) resp = json.load(conn) print resp['status']
เชือ่มตอ่พอร�ตอน�กรม
ร�บและจำาแนกขอ้มลู
โครงของเว็บแอพ (ui.py)● HTTP request > สบืคน้และจ�ดรปูขอ้มลู > HTML
import webapp2import osimport jinja2
JINJA_ENV = jinja2.Environment(loader= jinja2.FileSystemLoader(os.path.dirname(__file__)),extensions=['jinja2.ext.autoescape'],autoescape=True)class ViewHandler(webapp2.RequestHandler): def get(self): ... self.response.write(template.render(resp))app = webapp2.WSGIApplication([ ('/ui/view', ViewHandler)], debug=True)
สบืคน้ขอ้มลูจาก datastore● ขอ้มลูถกูจ�ดเกบ็ในรปู object อา้งอ�งไดด้ว้ย key● ใชเ้ทคน�ค templating เพือ่อา่นไฟล� HTML ตน้แบบ
from model import *
sn = self.request.get('sn')if sn != '': resp = {'status':'OK'} query = MyData.query(MyData.serial==int(sn)) data = query.fetch(10) resp['values'] = [entity.value for entity in data]else: resp = {'status':'ERR'}template = JINJA_ENV.get_template('index.html')
กำาหนดเงือ่นไข/สบืคน้
ไฟล� HTML ตน้แบบ
● ใชก้ารทดแทน HTML tag ในระหวา่ง renderingทดแทนต�วแปร<div data-role="footer"> <h2>{{status}}</h2></div>
แบบรายการ list, tuple<ui> {% for value in values %} <li>Value is {{value}}</li> {% endfor %}</ui>
ดงึคา่ต�วแปรมาแสดง
วนสรา้งรายการจาก list
ต�ดต�ง้เครือ่งมอืสำาหร�บ Python● พ�มพ�คำาส�ง่เพือ่ต�ดต�ง้ต�วต�ดต�ง้แพคเกจเฉพาะ Python
sudo apt-get install python-pip● ต�ดต�ง้ idlex โดยพ�มพ�คำาส�ง่
sudo pip install idlex
●