Get Alive

Get Alive

lenna

זו Lenna. כל מאמר שעוסק ב- image proccessing משתמש בתמונה הזו להדגמה (למה? תקראו בלינק).

אני רוצה להראות עליה איך יוצרים מתמונה רגילה, animated gif:

fadeinlenna

תאוריה

בתור התחלה, צריך ליצור תמונת בסיס ממנה ניצור את הפריימים שיהיו ב- gif הסופי:

  • נמיר את התמונה המקורית ל- grayscale (שחור-לבן).
  • נפעיל על התמונה פילטר של ״זיהוי קצוות״ (edge detection). ישנם מספר פילטרים שעושים את זה, כמו sobel או canny. הרעיון הוא, שהם מדגישים את השינויים הבולטים בין הפיקסלים בתמונה. בצורה כזו נקבל מעין "sketch" של התמונה.
  • התוצר של edge detection יהיה לבן על רקע שחור, לכן נעשה inversion (היפוך צבעים) – ונקבל את תמונת הבסיס.

תכלס

בשביל המימוש נשתמש בספריית Pillow, שהיא fork מתוך PIL הותיקה – ספריית python לעיבוד תמונה.

נפתח טרמינל python, ונתחיל עם הפקודות הבאות:

>>> from PIL import Image
>>> im = Image.open("Lenna.png")

נשנה את התמונה ל- grayscale, כלומר שרק חלק ה- Luminance יישאר:

>>> im = im.convert("L")
>>> im.show()

וזו התוצאה:

screen-shot-2016-11-30-at-15-03-24

נפעיל את ה- edge detection המובנה של PIL:

>>> from PIL import ImageFilter
>>> im = im.filter(ImageFilter.FIND_EDGES)
>>> im.show()

ונקבל:

screen-shot-2016-11-30-at-15-06-36

מה שנשאר זה רק לעשות invert, כלומר להפוך את הצבעים:

>>> from PIL import ImageOps
>>> im = ImageOps.invert(im)
>>> im.show()

זו התמונה הסופית איתה נעבוד:

screen-shot-2016-11-30-at-15-07-24

סקריפט

נרכז את כל הקוד הנ״ל לסקריפט הזה:

import sys, os, shutil
import numpy as np
from PIL import Image, ImageFilter, ImageOps
 
if len(sys.argv) > 1:
    imageFileName = sys.argv[1]
else:
    print "arg not found."
    sys.exit()
# load image
original = Image.open(imageFileName)
# convert to grayscale -> find edges -> invert
edges = ImageOps.invert(original.convert("L").filter(ImageFilter.FIND_EDGES))

התמונה שיש לנו היא שחור לבן, כלומר ערכים בין 0 ל- 255. נכתוב פונקציה שמקבלת ערך סף (threshold) ויוצרת תמונה חדשה שבה כל מה שמעל ה- threshold יהיה צבע רקע. ככל שערך הסף נמוך יותר, נראה פחות מהציור ויותר מהרקע.

def changeColors(image, bgColor, threshold):
    # Because we want to support in colorful gifs - to allow rgb bgColor -
    # we covert the image back to rgb
    image = image.convert("RGB")
    # We use numpy to work with the image data array directly
    data = np.array(image)
    # Get the three channels of the image to compare to
    red, green, blue = data[:,:,0], data[:,:,1], data[:,:,2]
    # Create mask to check if all channels (that probably all the same)
    # are bigger than threshold
    bgMask = (red > threshold) & (green > threshold) & (blue > threshold)
    # Replace all pixels that fit for the criteria with the bg color
    data[:,:,:3][bgMask] = bgColor
    # create back image from data array
    return Image.fromarray(data)

הרעיון הוא ליצור סט של פריימים בכל פעם עם threshold יותר גבוה, כך שבכל פריים נראה יותר מהתמונה. נשמור את כל הפריימים בתיקיה זמנית, ואחרי שניצור את ה- gif נמחק אותה.

folderName = "gifTemp"
filePath = "{0}/{1}.png"
for threshold in range(0, 255, 25):
    changeColors(image.copy(), bgColor, threshold).save(filePath.format(folderName, index))
    index += 1

אחרי שהרצנו את הסקריפט יש לנו תיקיה עם פריימים (בפורמט png). עכשיו צריך ליצור מהם gif.

בעיקרון, יש דרך ליצור מתוך python קבצי animated gif. לדוגמא images2gif. אבל בכל האפשרויות שניסיתי, לא הייתי מרוצה – מבחינת איכות ונפח הקובץ של התוצאה.

לכן בחרתי להשתמש בכלים חיצוניים ffmpeg ו- gifsicle. הם open source, וקיימים בדר״כ בסביבות פיתוח (לפחות ffmpeg).

נוסיף לסקריפט שלנו שתי פקודות:

os.system("ffmpeg -i " + folderName + "/%01d.png gifResults/fadeIn.gif")

הפקודה הזו מריצה את ffmpeg שלוקח את כל קבצי png שקיימים בתיקייה ומתחילים במספר – לפי הסדר – ויוצר מהם gif בשם fadeIn.

עכשיו הבעיה היא התזמון. אנחנו רוצים שכל הפריימים יהיו אותו זמן, חוץ מהאחרון – שם אנחנו רוצים להתעכב קצת, שנוכל לשים לב לתמונה הסופית. בשביל זה נריץ את gifsicle:

os.system("gifsicle -b gifResults/fadeIn.gif -d10 '#0--2' -d75 '#-1'")

פריים "-1" – כלומר הפריים האחרון מהסוף יהיה 0.75 שנייה (שנייה שלמה זה 100), וכל שאר הפריימים יהיו עשירית שנייה.

אפשר למצוא את הסקריפט המלא כאן.

 

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *