Python: การอ่านค่าสีในแต่ละ Pixel ของภาพด้วย PILlow

สวัสดีครับ
หลังๆ มานี่ผมไม่ได้เขียน Blog เลย เหมือนเคยครับ งานยุ่งจริงๆ (จริงๆ ช่วงนี้ก็สอบปลายภาคด้วยล่ะครับ แต่นอนไม่หลับ เวลาชีวิตรวนตั้งแต่ทำโปรเจ็กแล้ว แหะๆ)

บทความวันนี้ก็เกี่ยวกับโปรเจ็กที่ว่านี่ล่ะครับ เป็นโปรเจ็กเกมบน FPGA แบบง่ายๆ (เรียกว่างานเผาก็ย่อมได้) ใครสนใจไปดูได้บน Github FPGA “IA Journy” game ครับผม แต่ที่ผมจะนำเสนอวันนี้คือ script สร้างโมดูลวาดภาพภาษา Verilog ด้วย Python อีกที

งานหลักๆ ก็ตามชื่อบทความนั่นแหละครับ คืออ่านขนาดภาพและไล่อ่านค่าสีของแต่ละ pixel ก่อนเข้าสมการ (เรอะ?) เพื่อกำหนดค่าบิตสีขาออกจากโมดูลครับ ส่วนสาเหตุที่ใช้ Python ก็เนื่องจากว่า docs และ Stack Overflow มันเยอะ เขียนเผาๆ ก็จบงานได้ล่ะ :v

#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import Tkinter
import tkFileDialog
from PIL import Image
import ImageDraw
import PIL.ImageOps
__DEFAULT_VAR_NAME__ = "var1"
__IS_MARK_CENTER__ = False
__INVERT_COLOR__ = False
def main():
try:
Tkinter.Tk().withdraw() # Close the root window
in_path = str(tkFileDialog.askopenfilename())
print("Opening: {0}".format(in_path))
outputpath = str(tkFileDialog.asksaveasfilename())
im = Image.open(in_path)
w, h = im.size
outputpath = outputpath.format(width=w, w=w, height=h, h=h)
with open(outputpath, 'w') as f:
print("Output: {0}".format(outputpath))
variableName = raw_input('Type variable name (enter to use "{0}" as variable name) :'.format(__DEFAULT_VAR_NAME__)).strip()
if variableName == "":
variableName = __DEFAULT_VAR_NAME__
if __INVERT_COLOR__:
r,g,b,a = im.split()
rgb_image = Image.merge('RGB', (r,g,b))
inverted_image = PIL.ImageOps.invert(rgb_image)
r2,g2,b2 = inverted_image.split()
im = Image.merge('RGBA', (r2,g2,b2,a))
rgb_im = im.convert('RGBA')
resultImage = Image.new('RGBA', (w, h))
draw = ImageDraw.Draw(resultImage)
# f.write("reg [{3}:0] {0}[{1}] = {2} ".format(variableName, h, '{', (w*2)-1 ))
f.write("module {0}(\n".format(variableName))
f.write(" input clk,\n")
f.write(" input wire [9:0] characterPositionX,\n")
f.write(" input wire [8:0] characterPositionY,\n")
f.write(" input wire [9:0] drawingPositionX,\n")
f.write(" input wire [8:0] drawingPositionY,\n")
f.write(" output reg [2:0] rgb\n")
f.write(");\n")
f.write(" reg [9:0] x;\n")
f.write(" reg [9:0] y;\n")
f.write(" initial begin\n")
f.write(" x = 'd0;\n")
f.write(" y = 'd0;\n")
f.write(" end\n")
f.write("\n")
f.write(" always @(posedge clk) begin\n")
f.write(" x <= (drawingPositionX – characterPositionX + {0});\n".format(int(w/2)+1))
f.write(" y <= (drawingPositionY – characterPositionY + {0});\n".format(int(h/2)+1))
if(__IS_MARK_CENTER__):
f.write(" if(");
f.write("x == {0:d} && y == {1:d} || ".format((w/2), (h/2)))
f.write("x == {0:d} && y == {1:d} || ".format((w/2), (h/2)1))
f.write("x == {0:d} && y == {1:d} || ".format((w/2), (h/2)+1))
f.write("x == {0:d} && y == {1:d} || ".format((w/2)1, (h/2)))
f.write("x == {0:d} && y == {1:d} || ".format((w/2)1, (h/2)1))
f.write("x == {0:d} && y == {1:d} || ".format((w/2)1, (h/2)+1))
f.write("x == {0:d} && y == {1:d} || ".format((w/2)+1, (h/2)))
f.write("x == {0:d} && y == {1:d} || ".format((w/2)+1, (h/2)1))
f.write("x == {0:d} && y == {1:d} )".format((w/2)+1, (h/2)+1))
f.write("\tbegin\trgb <= 3'b010;\tend\n")
i = 0
for y in xrange(1,h):
# f.write("{0}'b".format((w*2)))
for x in xrange(1,w):
r, g, b, alpha = rgb_im.getpixel((x, y))
# grayScale = r * 299.0/1000 + g * 587.0/1000 + b * 114.0/1000
# grayScale = int(grayScale/256*4)
# grayScale = int(grayScale/256*8)
grayScale = 0;
if r > 125:
grayScale += 4
if g > 125:
grayScale += 2
if b > 125:
grayScale += 1
print "Pixel: ({0},{1}) = ({2},{3},{4}, {6}) => {5:03b}".format(x, y, r, g, b, grayScale, alpha)
draw.point((x,y), (255 if r > 125 else 0, 255 if g > 125 else 0, 255 if b > 125 else 0, alpha))
# f.write("{0}{1}".format('1' if grayScale >= 2 else '0', '1' if grayScale%2 == 1 else '0'))
if(alpha > 128 and grayScale > 0) :
f.write("\t\t")
if(__IS_MARK_CENTER__ or i > 0): f.write("else ")
f.write("if(x=={0} && y=={1}) begin\trgb <= 3'b{2}{3}{4}; end\n".format(x, y, 1 if r > 125 else 0, 1 if g > 125 else 0, 1 if b > 125 else 0))
i += 1
f.write(" else begin rgb <= 3'b000; end// Width: {0}, Height: {1} From: {2}\n".format(w,h,in_path))
f.write(" end\nendmodule\n")
f.close()
resultImage.save(outputpath+".png", 'PNG')
os.system("start "+outputpath)
os.system("start "+outputpath+".png")
except Exception, e:
print e
if __name__ == "__main__":
main()
# im = Image.open('image.gif')
# rgb_im = im.convert('RGB')

ขั้นตอนหลักในการทำงานของ Script มีดังนี้ครับ
1. รับ file path ของทั้งขาเข้าและขาออก (ขาเข้าเป็นภาพอะไรก็ได้ที่ PILlow รองรับ jpg, gif, png, bmp, ฯลฯ ส่วนขาออก จริงๆ ตั้งชื่อและนามสกุลไฟล์แบบไหนก็ได้ แต่ปกติ Verilog module ก็ใช้ .v ครับ และมีพ่วง .v.png ไว้พรีวิวว่าภาพจริง ๆ จะมีขนาดและสีสันอย่างไร) งานนี้ผมใช้ tkFileDialog รับค่าเข้ามานะครับ (เท่าที่ทราบใช้ได้เฉพาะบน Python 2 เท่านั้น) เพราะใช้ง่าย เรียก method tkFileDialog.askopenfilename() เดียวเอาอยู่เลย
2. ขอชื่อ module ขาออก ตรงนี้แค่ raw_input ง่ายๆ ครับ ไม่มีอะไรพิเศษ
3. อ่านค่าเบื้องต้นของภาพ (ขนาดภาพ) ใช้ .size อ่านจาก Image object (ในที่นี้คือ Image.open(in_path))
4. แปลง format ภาพเป็น RGBA (ต้องเก็บค่าความใส–alpha ไว้ใช้เช็คว่าทึบรึเปล่ากรณี gif และ png ครับ) ใช้ .convert('RGBA')
5. อ่านค่าสี RGBA ด้วย .getpixel((x, y)) แล้วเอาค่ามาเข้า condition ดูว่าจะให้บิตสีออกเป็นอะไร ถ้าสีใดเกินกึ่งหนึ่งของสี (หรือ 127 ขึ้นไป) และค่าความใสเกินกึ่งหนึ่งเช่นกัน ก็ให้บิตสีนั้นเป็น 1 ไป
6. เช็คบิตสีผลลัพธ์ว่าเกิน 2’b000 รึเปล่า ถ้าเกินก็ให้โมดูลเปลี่ยนสีให้ เพราะสีดำค่อยเข้า else เอา
7. พ่นไฟล์และภาพพรีวิวออกมาเลย

ตัวอย่างการใช้ image2verilog ซึ่งใช้การหาพอกเซลดังกล่าวข้างต้น

ตัวอย่างการใช้ image2verilog ซึ่งใช้การหาพอกเซลดังกล่าวข้างต้น

เช่นเคยครับ มีข้อสงสัย comment เข้ามาได้ครับผม 🙂