[TH] The MaixPy’s image class Part 1. draw and find something.

บทความนี้กล่าวถึงการใช้คลาส image กับโมดูลแสดงผล TFT-LCD ของบอร์ด Sipeed M1W dock suit ผ่านทางคลาส lcd (MaixPy’s lcd class) ดังภาพที่ 1 ที่มีมากับ MaixPy เพื่อศึกษารายการคำสั่งที่คลาส image เตรียมไว้ให้ และตัวอย่างโปรแกรมการใช้งานคำสั่งเกี่ยวกับการสร้างวัตถุบัฟเฟอร์ การล้างค่าในบัฟเฟอร์ การลบบัฟเฟอร์ การวาดเส้นตรง วงกลม สี่เเหลี่ยม แสดงตัวอักษร การบันทึกข้อมูลจากบัฟเฟอร์ลงการ์ดหน่วยความจำ (microSD Card) การค้นหาเส้นตรงในบัฟเฟอร์ (find_lines) การค้นหาวงกลมในบัฟเฟอร์ (find_circles) และการค้นหาสี่เหลี่ยม (find_rects) ในบัฟเฟอร์ด้วยฟังก์ชันทำงานที่มีมาให้ ซึ่งใช้หลักการของ Hough Transform เพื่อหาตำแหน่งและพารามิเตอร์ของวัตถุที่ค้นหา

ภาพที่ 1 บอร์ด Sipeed M1W dock suit
ภาพที่ 1 บอร์ด Sipeed M1W dock suit

image class

คลาส image เป็นคลาสสำหรับสร้างบัฟเฟอร์ของหน่วยความจำสำหรับเก็บข้อมูลภาพ และสามารถนำไปแสดงผลที่โมดูล lcd ด้วยคำสั่ง lcd.display( ) รายการคำสั่งที่ควรรู้จักและใช้งานได้ มีดังต่อไปนี้

คำสั่งเกี่ยวกับบัฟเฟอร์

Image()

คำสั่งสำหรับสร้างบัฟเฟอร์มี 2 รูปแบบ คือ สร้างเป้นบัฟเฟอร์ว่างเปล่า หรือสร้างจากภาพที่ต้องการ ดังนี้

บัฟเฟอร์ = image.Image()

บัฟเฟอร์ = image.Image( ชื่อภาพ )

ตัวอย่างการสร้างวัตถุบัฟเฟอร์ภาพเขียนได้ดังนี้

a = image.Image()

เมื่อสั่งแสดงผล a ด้วยคำสั่ง print(a) แล้วได้ผลลัพธ์ดังนี้

{"w":320, "h":240, "type"="rgb565", "size":153600}

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

>>> import gc, image
>>> gc.enable()
>>> gc.collect()
>>> print(gc.mem_free())
513184
>>> img = image.Image()
>>> print(gc.mem_free())
356928
>>> img = None
>>> print(gc.mem_free())
356640
>>> gc.collect()
>>> print(gc.mem_free())
512864

จากโค้ดด้านบนจะพบว่า ตอนแรกเริ่มนั้นหน่วยความจำมีอยู่ 513,184 ไบต์ เมื่อสร้างตัวแปรบัฟเฟอร์ชื่อ img ทำให้เหลือ 356,928 แต่เมื่อกำหนดให้บัฟเฟอร์เป็น None เมื่อสั่งตรวจสอบปริมาณหน่วยความจำยังคงเป็น 356,640 จึงได้ทดลองสั่ง gc.collect() อีกครั้งจึงทำให้หน่วยความจำเหลือ 512,864 ดังนั้น เมื่อกำหนดให้ตัวแปรบัฟเฟอร์เป็น None ต้องสั่ง gc.colelct() เพื่อให้ระบบบริหารจัดการหน่วยความจำใหม่อีกครั้งด้วย ซึ่งการทำแบบนี้ทำให้โปรแกรมทำงานช้าลงแต่แลกกับการบริหารหน่วยความจำที่ดีขึ้น

cut()

คำสั่ง cut() ใช้สำหรับตัดส่วนของบัฟเฟอร์ในกรอบที่กำหนด โดยรูปแบบของคำสั่ง เป็นดังนี้

บัฟเฟอร์ = วัตถุ.cut( x, y, w, h )

ตัวอย่างการแบ่งข้อมูลภาพออกเป็น 2 ส่วน เขียนดังนี้

import image
img = image.Image()
upImg = img.cut(0,0,img.width(),img.height()/2)
dwImg = img.cur(0,img.height()/2,img.width(),img.height()/2)

save()

กรณีที่ต้องการบันทึกบัฟเฟอร์ลงไฟล์สามารถสั่งงานได้ตามรูปแบบต่อไปนี้

วัตถุบัฟเฟอร์.save( “/sd/ชื่อไฟล์.jpg”)

width()

คำสั่ง width() คืนค่าความกว้างของบัฟเฟอร์

height()

คำสั่ง height() ทำหน้าที่คืนค่าความสูงของบัฟเฟอร์

format()

คำสั่ง format() ใช้สำหรับคืนค่ารูปแบบของข้อมูลที่เก็บในบัฟเฟอร์ ซึ่งมีดังนี้

  • sensor.GRAYSCALE
  • sensor.RGB565
  • sensor.BAYER
  • sensor.JPEG

size()

คำสั่ง size() เป็นคำสั่งรายงานจำนวนไบต์ของหน่วยความจำที่ถูกใช้เพื่อเก็บข้อมูลบัฟเฟอร์

get_pixel()

คำสั่ง get_pixel() ใช้สำหรับอ่านค่าสี R,G,B จากตำแหน่ง (x,y) ที่ระบุ โดบรูปแบบของการใช้งานเป็นดังนี้

(r, g, b) = วัตถุบัฟเฟอร์.get_pixel( x, y )

set_pixel()

กรณีที่ต้องการเปลี่ยนค่าพิกเซลที่ตำแหน่ง (x,y) ให้เป็นค่าสี (r,g,b) สามารถใช้คำสั่ง set_pixel() ตามรูปแบบการใช้งานดังนี้

วัตถุบัฟเฟอร์.set_pixel( x, y, (r,g,b) )

คำสั่งวาด

draw_string()

คำสั่งแสดงข้อความหรือวาดข้อความ ณ ตำแหน่ง (x,y) ด้วยข้อความ str โดยกำหนดค่าสีและขนาดของตัวอักษร มีรูปแบบของการใช้งานดังนี้

วัตถุบัฟเฟอร์.draw_string( x, y, str, (r,g,b), ขนาดตัวอักษร0

draw_line()

คำสั่งสำหรับวาดเส้นตรงในบัฟเฟอร์มีรูปแบบของการวาดจาก (x1,y1) ไป (x2,y2) ดังนี้

วัตถุบัฟเฟอร์.draw_line( x1, y1, x2, y2 )

หรือ
วัตถุบัฟเฟอร์.draw_line( x1, y1, x2, y2, (r,g,b) )

หรือ
วัตถุบัฟเฟอร์.draw_line( x1, y1, x2, y2, (r,g,b), ความหนาของเส้น )

draw_circle()

คำสั่งสำหรับวาดวงกลมที่จุดศูนย์กลางอยู่ที่ (x,y) มีรัศมี r สั่งงานได้ดังนี้ โดยในรูปแบบที่ 4 สามารถกำหนดสถานะการเติมสีเป็น True เพื่อเติมสีลงในวงกลม แต่ถ้าเป็น False จะเป็นวงกลมที่มีแต่เส้นขอบ

วัตถุบัฟเฟอร์.draw_circle( x, y, r)

หรือ
วัตถุบัฟเฟอร์.draw_circle(x,y,r,(rr,gg,bb))

หรือ
วัตถุบัฟเฟอร์.draw_circle(x,y,r,(rr,gg,bb),ความหนา)

หรือ
วัตถุบัฟเฟอร์.draw_circle(x,y,r,(rr,gg,bb),ความหนา, สถานะการเติมสี)

draw_rectangle()

คำสั่งสำหรับวาดสี่เหลี่ยมขนาด w,h โดยมุมซ้ายของสี่เหลี่ยมอยู่ที่จำแหน่ง (x,y) มีรูปแบบของการใช้งานดังนี้

วัตถุบัฟเฟอร์.draw_rectangle( x, y, w, h )

หรือ
วัตถุบัฟเฟอร์.draw_rectangle( x, y, w, h, (rr, gg, bb) )

หรือ
วัตถุบัฟเฟอร์.draw_rectangle( x, y, w, h, (rr, gg, bb), ความหนา )

หรือ
วัตถุบัฟเฟอร์.draw_rectangle( x, y, w, h, (rr, gg, bb) , ความหนา, สถานะการเติมสี)

clear()

คำสั่งล้างค่าที่เก็บในบัฟเฟอร์มีรูปแบบการใช้ดังต่อไปนี้

วัตถุบัฟเฟอร์.clear()

draw_cross()

คำสั่งสำหรับวาดเครื่องหมายกากบาทที่กึ่งกลางอยู่ที่ตำแหน่ง (x,y) ขนาด n มีรูปแบบการใช้งานดังนี้

วัตถุบัฟเฟอร์.draw_cross( x, y, n, (r,g,b), ความหนา=1)

คำสั่งค้นหา

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

find_lines()

รูปแบบของคำสั่งค้นหาเส้นตรงในวัตถุบัฟเฟอร์ด้วยคำสั่ง find_lines() และส่งคืนกลับเป็นลิสต์ของข้อมูล เป็นดังนี้

รายการที่พบ = วัตถุบัฟเฟอร์.find_lines()

หรือ
รายการที่พบ = วัตถุบัฟเฟอร์.find_lines((x,y,w,h))

ลิสต์ที่ได้กลับมามีจำนวนเท่ากับจำนวนเส้นตรงที่พบ โดยแต่ละเส้นที่พบนั้นมีข้อมูลดังนี้

  • x1 ค่า x เริ่มต้น
  • y1 ค่า y เริ่มต้น
  • x2 ค่า x สิ้นสุด
  • y2 ค่า y สิ้นสุด
  • length ความยาวของเส้นตรง
  • magnitude เป็นตัวบอกความคล้ายของวัตถุที่ค้นหา ยิ่งมีค่ามากยิ่งดี
  • theta ค่ามุม
  • rho

find_circles()

กรณีที่ต้องการค้นหาวัตถุประเภทวงกลมในบัฟเฟอร์สามารถกระทำได้โดยใช้งานคำสั่ง find_circules() ตามรูปแบบของคำสั่งต่อไปนี้

รายการที่พบ = วัตถุบัฟเฟอร์.find_circles( )

หรือ
รายการที่พบ = วัตถุบัฟเฟอร์.find_circlrs( (x, y, w, h) )

ผลลัพธ์จากการค้นหาวงกลมจะคินกลับมาในรูปแบบของลิสต์เช่นเดียวกันกับการค้นหาเส้นตรง แต่รายการข้อมูลในลิสต์มีความแตกต่างกัน คือ รายการที่ได้ประกอบด้วย จุดกึ่งกลาง (x,y) รัศมี และค่า magnitude ดังภาพที่ 5

find_rects()

การค้นหาสี่เหลี่ยมที่อยู่ในบัฟเฟอร์ด้วยคำสั่ง find_rects() มีรูปแบบของการใช้งานดังนี้

รายการที่พบ = วัตถุบัฟเฟอร์.find_rects()

หรือ
รายการที่พบ = วัตถุบัฟเฟอร์.find_rects((x,y,w,h))

โดยผลลัพธ์จกาการค้นหานั้นอยู่ในรูปแบบของตัวแปรลิสต์ของรายการต่อไปนี้

  • x ค่าแกน x ของมุมซ้ายบน
  • y ค่าแกน y ของมุมซ้ายบน
  • w ค่าความกว้าง
  • h ค่าความสูง
  • magnitude เป็นตัวบอกความคล้ายของวัตถุที่ค้นหา ยิ่งมีค่ามากยิ่งดี

ตัวอย่างโปรแกรม

ตัวอย่างโปรแกรม img1

ตัวอย่างที่ 1 เป็นการวาดหน้าจอด้วย draw_string() และแสดงอัตราการแสดงภาพต่อ 1 วินาที หรือ fps (frame per second) โดยหลักการของการวัดอัตราการอัพเดตหน้าจอที่เป็นหน่วยจำนวนภาพต่อวินาทีต้องนำเข้าคลาส time และเรียกใช้คำสั่งต่อไปนี้

  • clock() สำหรับอ่านค่าเวลา
  • tick() ในทำการนับ
  • fps() รายงานจำนวนภาพที่แสดงผลใน 1 วินาที

โค้ดโปรแกรมของการแสดงข้อความและอัตราการแสดงผลเป็นดังนี้ โดยตัวอย่างผลลัพธ์เป็นดังภาพที่ 2

# img1 - By: JarutEx - Mon Oct 4 2021

import lcd, image, time

lcd.freq(80000000)
lcd.init(color=(255,0,0))
clock = time.clock()
img = image.Image()

while(True):
    clock.tick()
    img.clear()
    img.draw_string(10,100,"JarutEx",(232,232,18),7)
    img.draw_string(10,20,"fps={}".format(clock.fps()),(252,252,252),2)
    lcd.display(img)
ภาพที่ 2 ตัวอย่างผลลัพธ์จาก img1.py
ภาพที่ 2 ตัวอย่างผลลัพธ์จาก img1.py

ตัวอย่างโปรแกรม img2

ในตัวอย่างโปรแกรม img2 เป็นการวาดเส้นตรงลงในบัฟเฟอร์ หลังจากนั้นทำการค้นหาเส้นตรงในนบัฟเฟอร์ ซึ่งผลลัพธ์ของการค้นหานั้นเป็นดังภาพที่ 3

# img2 - By: JarutEx - Mon Oct 4 2021

import lcd, image, time, gc, machine

gc.enable()
gc.collect()

lcd.freq(80000000)
lcd.init(color=(255,0,0))
img = image.Image()

fLines = None
gc.collect()
img.clear()
img.draw_line( 10, 10, 200, 100, coloe=(255,255,255))
fLines = img.find_lines((0,0,img.width(),img.height()))
nLines = len(fLines)
if (nLines > 0):
    img.draw_string(10,10,"lines={}".format(nLines),(8,242,232),2)
    for i in range(nLines):
        print("{}".format(fLines[i]))
lcd.display(img)
time.sleep(2)
fLines = None
img = None
lcd.deinit()
gc.collect()
machine.reset()
ภาพที่ 3 ตัวอย่างผลลัพธ์ของการค้นภาเส้นตรงภายในบัฟเฟอร์
ภาพที่ 3 ตัวอย่างผลลัพธ์ของการค้นหาเส้นตรงภายในบัฟเฟอร์

ตัวอย่างโปรแกรม img3

ตัวอย่างโปรแกรม img3 เเพิ่มความซับซ้อนของโปรแกรมด้วยการสุ่มจำนวนวงกลม ตำแหน่งจุดศูนย์กลางของวงกลม และรัศมีของวงกลมและวาดลงบัฟเฟอร์ ซึ่งได้ตัวอย่างของการวาดเป็นดังภาพที่ 4 หลังจากนั้นทำการค้นหาวงกลมในบัฟเฟอร์ได้ตัวอย่างผลลัพธ์ของการค้นหาเป็นดังภาพที่ 5

# img3 - By: JarutEx - Mon Oct 4 2021

import lcd, image, time, gc, machine
import random

gc.enable()
gc.collect()

lcd.freq(80000000)
lcd.init(color=(255,0,0))
img = image.Image()

fLines = None
gc.collect()
img.clear()
for i in range(1+random.getrandbits(4)):
    img.draw_circle( random.getrandbits(7)+20, random.getrandbits(7)+20,
    random.getrandbits(4)+4, (255,255,255), 1)
fCircles = img.find_circles((0,0,img.width(),img.height()))
nCircles = len(fCircles)
if (nCircles > 0):
    for i in range(nCircles):
        print("{}".format(fCircles[i]))
lcd.display(img)
time.sleep(5)
fCircles = None
img = None
lcd.deinit()
gc.collect()
machine.reset()

img.draw_circle(x,y,r,(rr,gg,bb),2,(rr,gg,bb))
ภาพที่ 4 ตัวอย่างการแสดงวงกลมที่ถูกสุ่มจำนวน ตำแหน่ง และรัศมีจาก img3.py
ภาพที่ 4 ตัวอย่างการแสดงวงกลมที่ถูกสุ่มจำนวน ตำแหน่ง และรัศมีจาก img3.py
ภาพที่ 5 ตัวอย่างผลลัพธ์ของการค้นหาวงกลมจากภาพที่ 4
ภาพที่ 5 ตัวอย่างผลลัพธ์ของการค้นหาวงกลมจากภาพที่ 4

ตัวอย่างโปรแกรม img4

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

# img4 - By: JarutEx - Mon Oct 4 2021

import lcd, image, time, gc, machine
import random

gc.enable()
gc.collect()

lcd.freq(80000000)
lcd.init(color=(255,0,0))
img = image.Image()

fLines = None
gc.collect()
img.clear()
for i in range(1+random.getrandbits(4)):
    img.draw_rectangle(
        random.getrandbits(8),random.getrandbits(8),
        4+random.getrandbits(6),4+random.getrandbits(6),
        (random.getrandbits(8),random.getrandbits(8),random.getrandbits(8)),
        random.getrandbits(2)+1,
        True)
fRects = img.find_rects((0,0,img.width(),img.height()))
nRects = len(fRects)
if (nRects > 0):
    for i in range(nRects):
        print("{}".format(fRects[i]))
lcd.display(img)
time.sleep(5)
fRects = None
img = None
lcd.deinit()
gc.collect()
machine.reset()
ภาพที่ 6 ตัวอย่างการสุ่มวาดสี่เหลี่ยม
ภาพที่ 6 ตัวอย่างการสุ่มวาดสี่เหลี่ยม
ภาพที่ 7 ตัวอย่างผลลัพธ์ของการค้นหาสี่เหลี่ยมจากภาพที่ 6
ภาพที่ 7 ตัวอย่างผลลัพธ์ของการค้นหาสี่เหลี่ยมจากภาพที่ 6

สรุป

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

ท่านใดต้องการพูดคุยสามารถคอมเมนท์ได้เลยครับ

แหล่งอ้างอิง

  1. Wikipedia : Hough transform
  2. OpenMV : MicroPython documentation
  3. OpenMV : Class image : image object

(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-10-05, 2021-12-19