Python 2 กับ PIL
สิ่งนี้ยังคงอยู่ในระหว่างการดำเนินการ นอกจากนี้รหัสด้านล่างนี้เป็นระเบียบที่น่ากลัวของสปาเก็ตตี้และไม่ควรใช้เป็นแรงบันดาลใจ :)
from PIL import Image, ImageFilter
from math import sqrt
from copy import copy
from random import shuffle, choice, seed
IN_FILE = "input.png"
OUT_FILE = "output.png"
LOGGING = True
GRAPHICAL_LOGGING = False
LOG_FILE_PREFIX = "out"
LOG_FILE_SUFFIX = ".png"
LOG_ROUND_INTERVAL = 150
LOG_FLIP_INTERVAL = 40000
N = 500
P = 30
BLUR_RADIUS = 3
FILAMENT_ROUND_INTERVAL = 5
seed(0) # Random seed
print("Opening input file...")
image = Image.open(IN_FILE).filter(ImageFilter.GaussianBlur(BLUR_RADIUS))
pixels = {}
width, height = image.size
for i in range(width):
    for j in range(height):
        pixels[(i, j)] = image.getpixel((i, j))
def dist_rgb((a,b,c), (d,e,f)):
    return (a-d)**2 + (b-e)**2 + (c-f)**2
def nbors((x,y)):
    if 0 < x:
        if 0 < y:
            yield (x-1,y-1)
        if y < height-1:
            yield (x-1,y+1)
    if x < width - 1:
        if 0 < y:
            yield (x+1,y-1)
        if y < height-1:
            yield (x+1,y+1)
def full_circ((x,y)):
    return ((x+1,y), (x+1,y+1), (x,y+1), (x-1,y+1), (x-1,y), (x-1,y-1), (x,y-1), (x+1,y-1))
class Region:
    def __init__(self):
        self.points = set()
        self.size = 0
        self.sum = (0,0,0)
    def flip_point(self, point):
        sum_r, sum_g, sum_b = self.sum
        r, g, b = pixels[point]
        if point in self.points:
            self.sum = (sum_r - r, sum_g - g, sum_b - b)
            self.size -= 1
            self.points.remove(point)
        else:
            self.sum = (sum_r + r, sum_g + g, sum_b + b)
            self.size += 1
            self.points.add(point)
    def mean_with(self, color):
        if color is None:
            s = float(self.size)
            r, g, b = self.sum
        else:
            s = float(self.size + 1)
            r, g, b = map(lambda a,b: a+b, self.sum, color)
        return (r/s, g/s, b/s)
print("Initializing regions...")
aspect_ratio = width / float(height)
a = int(sqrt(N)*aspect_ratio)
b = int(sqrt(N)/aspect_ratio)
num_components = a*b
owners = {}
regions = [Region() for i in range(P)]
borders = set()
nodes = [(i,j) for i in range(a) for j in range(b)]
shuffle(nodes)
node_values = {(i,j):None for i in range(a) for j in range(b)}
for i in range(P):
    node_values[nodes[i]] = regions[i]
for (i,j) in nodes[P:]:
    forbiddens = set()
    for node in (i,j-1), (i,j+1), (i-1,j), (i+1,j):
        if node in node_values and node_values[node] is not None:
            forbiddens.add(node_values[node])
    node_values[(i,j)] = choice(list(set(regions) - forbiddens))
for (i,j) in nodes:
    for x in range((width*i)/a, (width*(i+1))/a):
        for y in range((height*j)/b, (height*(j+1))/b):
            owner = node_values[(i,j)]
            owner.flip_point((x,y))
            owners[(x,y)] = owner
def recalc_borders(point = None):
    global borders
    if point is None:
        borders = set()
        for i in range(width):
            for j in range(height):
                if (i,j) not in borders:
                    owner = owner_of((i,j))
                    for pt in nbors((i,j)):
                        if owner_of(pt) != owner:
                            borders.add((i,j))
                            borders.add(pt)
                            break
    else:
        for pt in nbors(point):
            owner = owner_of(pt)
            for pt2 in nbors(pt):
                if owner_of(pt2) != owner:
                    borders.add(pt)
                    break
            else:
                borders.discard(pt)
def owner_of(point):
    if 0 <= point[0] < width and 0 <= point[1] < height:
        return owners[point]
    else:
        return None
# Status codes for analysis
SINGLETON = 0
FILAMENT = 1
SWAPPABLE = 2
NOT_SWAPPABLE = 3
def analyze_nbors(point):
    owner = owner_of(point)
    circ = a,b,c,d,e,f,g,h = full_circ(point)
    oa,ob,oc,od,oe,of,og,oh = map(owner_of, circ)
    nbor_owners = set([oa,oc,oe,og])
    if owner not in nbor_owners:
        return SINGLETON, owner, nbor_owners - set([None])
    if oc != oe == owner == oa != og != oc:
        return FILAMENT, owner, set([og, oc]) - set([None])
    if oe != oc == owner == og != oa != oe:
        return FILAMENT, owner, set([oe, oa]) - set([None])
    last_owner = oa
    flips = {last_owner:0}
    for (corner, side, corner_owner, side_owner) in (b,c,ob,oc), (d,e,od,oe), (f,g,of,og), (h,a,oh,oa):
        if side_owner not in flips:
            flips[side_owner] = 0
        if side_owner != corner_owner or side_owner != last_owner:
            flips[side_owner] += 1
            flips[last_owner] += 1
        last_owner = side_owner
    candidates = set(own for own in flips if flips[own] == 2 and own is not None)
    if owner in candidates:
        return SWAPPABLE, owner, candidates - set([owner])
    return NOT_SWAPPABLE, None, None
print("Calculating borders...")
recalc_borders()
print("Deforming regions...")
def assign_colors():
    used_colors = {}
    for region in regions:
        r, g, b = region.mean_with(None)
        r, g, b = int(round(r)), int(round(g)), int(round(b))
        if (r,g,b) in used_colors:
            for color in sorted([(r2, g2, b2) for r2 in range(256) for g2 in range(256) for b2 in range(256)], key=lambda color: dist_rgb(color, (r,g,b))):
                if color not in used_colors:
                    used_colors[color] = region.points
                    break
        else:
            used_colors[(r,g,b)] = region.points
    return used_colors
def make_image(colors):
    img = Image.new("RGB", image.size)
    for color in colors:
        for point in colors[color]:
            img.putpixel(point, color)
    return img
# Round status labels
FULL_ROUND = 0
NEIGHBOR_ROUND = 1
FILAMENT_ROUND = 2
max_filament = None
next_search = set()
rounds = 0
points_flipped = 0
singletons = 0
filaments = 0
flip_milestone = 0
logs = 0
while True:
    if LOGGING and (rounds % LOG_ROUND_INTERVAL == 0 or points_flipped >= flip_milestone):
        print("Round %d of deformation:\n %d edit(s) so far, of which %d singleton removal(s) and %d filament cut(s)."%(rounds, points_flipped, singletons, filaments))
        while points_flipped >= flip_milestone: flip_milestone += LOG_FLIP_INTERVAL
        if GRAPHICAL_LOGGING:
            make_image(assign_colors()).save(LOG_FILE_PREFIX + str(logs) + LOG_FILE_SUFFIX)
            logs += 1
    if max_filament is None or (round_status == NEIGHBOR_ROUND and rounds%FILAMENT_ROUND_INTERVAL != 0):
        search_space, round_status = (next_search & borders, NEIGHBOR_ROUND) if next_search else (copy(borders), FULL_ROUND)
        next_search = set()
        max_filament = None
    else:
        round_status = FILAMENT_ROUND
        search_space = set([max_filament[0]]) & borders
    search_space = list(search_space)
    shuffle(search_space)
    for point in search_space:
        status, owner, takers = analyze_nbors(point)
        if (status == FILAMENT and num_components < N) or status in (SINGLETON, SWAPPABLE):
            color = pixels[point]
            takers_list = list(takers)
            shuffle(takers_list)
            for taker in takers_list:
                dist = dist_rgb(color, owner.mean_with(None)) - dist_rgb(color, taker.mean_with(color))
                if dist > 0:
                    if status != FILAMENT or round_status == FILAMENT_ROUND:
                        found = True
                        owner.flip_point(point)
                        taker.flip_point(point)
                        owners[point] = taker
                        recalc_borders(point)
                        next_search.add(point)
                        for nbor in full_circ(point):
                            next_search.add(nbor)
                        points_flipped += 1
                    if status == FILAMENT:
                        if round_status == FILAMENT_ROUND:
                            num_components += 1
                            filaments += 1
                        elif max_filament is None or max_filament[1] < dist:
                            max_filament = (point, dist)
                    if status == SINGLETON:
                        num_components -= 1
                        singletons += 1
                    break
    rounds += 1
    if round_status == FILAMENT_ROUND:
        max_filament = None
    if round_status == FULL_ROUND and max_filament is None and not next_search:
        break
print("Deformation completed after %d rounds:\n %d edit(s), of which %d singleton removal(s) and %d filament cut(s)."%(rounds, points_flipped, singletons, filaments))
print("Assigning colors...")
used_colors = assign_colors()
print("Producing output...")
make_image(used_colors).save(OUT_FILE)
print("Done!")
มันทำงานอย่างไร
โปรแกรมแบ่งผืนผ้าใบออกเป็นPส่วน ๆ แต่ละส่วนประกอบด้วยเซลล์จำนวนหนึ่งโดยไม่มีรู ในขั้นต้นผ้าใบจะถูกแบ่งออกเป็นสี่เหลี่ยมประมาณซึ่งจะถูกกำหนดแบบสุ่มไปยังภูมิภาค จากนั้นภูมิภาคเหล่านี้จะ "ผิดรูป" ในกระบวนการวนซ้ำซึ่งพิกเซลที่กำหนดสามารถเปลี่ยนภูมิภาคได้หาก
- การเปลี่ยนแปลงจะลดระยะห่างของพิกเซล RGB จากสีเฉลี่ยของพื้นที่ที่มีและ
- มันไม่ได้ทำลายหรือรวมเซลล์หรือแนะนำหลุมในพวกเขา
เงื่อนไขหลังสามารถบังคับใช้แบบโลคัลดังนั้นกระบวนการจึงมีลักษณะคล้ายกับหุ่นยนต์เซลลูลาร์ ด้วยวิธีนี้เราไม่ต้องทำสิ่งกีดขวางหรือสิ่งใดก็ตามที่เร่งกระบวนการขึ้นอย่างมาก อย่างไรก็ตามเนื่องจากเซลล์ไม่สามารถแตกตัวได้บางส่วนจึงกลายเป็น "เส้นใย" ที่กั้นเซลล์อื่นและยับยั้งการเติบโตของเซลล์ ในการแก้ไขปัญหานี้มีกระบวนการที่เรียกว่า "filament cut" ซึ่งบางครั้งจะแบ่งเซลล์ที่มีรูปร่างเป็นเส้นใยออกเป็นสองส่วนถ้ามีน้อยกว่าNเซลล์ในเวลานั้น เซลล์ยังสามารถหายไปได้ถ้าขนาดของมันคือ 1 และนี่ทำให้พื้นที่สำหรับเส้นใยลดลง
กระบวนการจะสิ้นสุดลงเมื่อไม่มีพิกเซลมีแรงจูงใจในการสลับพื้นที่และหลังจากนั้นแต่ละพื้นที่จะถูกระบายสีด้วยสีโดยเฉลี่ย โดยปกติจะมีเส้นใยบางส่วนที่เหลืออยู่ในเอาท์พุทที่สามารถเห็นได้ในตัวอย่างด้านล่างโดยเฉพาะอย่างยิ่งในเนบิวลา
P = 30, N = 500
 
 
 

รูปภาพเพิ่มเติมในภายหลัง
คุณสมบัติที่น่าสนใจของโปรแกรมของฉันคือความน่าจะเป็นดังนั้นผลลัพธ์อาจแตกต่างกันระหว่างการทำงานที่แตกต่างกันเว้นแต่คุณจะใช้เมล็ดพันธุ์เทียมเทียมเดียวกันแน่นอน การสุ่มนั้นไม่จำเป็น แต่ฉันแค่ต้องการหลีกเลี่ยงสิ่งประดิษฐ์โดยไม่ตั้งใจซึ่งอาจเป็นผลมาจากวิธีการที่ Python สำรวจชุดพิกัดหรือสิ่งที่คล้ายกัน โปรแกรมมีแนวโน้มที่จะใช้ทุกPสีและเกือบทุกNเซลล์และเซลล์ไม่เคยมีรูตามการออกแบบ นอกจากนี้กระบวนการเปลี่ยนรูปยังค่อนข้างช้า ลูกบอลสีใช้เวลาเกือบ 15 นาทีในการผลิตบนเครื่องของฉัน กลับหัวกลับหางคุณเปิดGRAPHICAL_LOGGINGตัวเลือกคุณจะได้รับชุดรูปภาพสวย ๆ ของกระบวนการเปลี่ยนรูป ฉันสร้างโมนาลิซ่าให้เป็นภาพเคลื่อนไหว GIF (ย่อขนาดลง 50% เพื่อลดขนาดไฟล์) หากคุณมองใบหน้าและเส้นผมของเธออย่างใกล้ชิดคุณสามารถมองเห็นกระบวนการตัดเส้นใยได้
