วาดรูปเป็นแผนที่ Voronoi


170

เพิ่มเครดิตให้กับงานอดิเรกของ Calvin ที่ผลักดันแนวคิดท้าทายของฉันในทิศทางที่ถูกต้อง

พิจารณาชุดของคะแนนในระนาบซึ่งเราจะเรียกไซต์และเชื่อมโยงสีกับแต่ละไซต์ ตอนนี้คุณสามารถทาสีระนาบทั้งหมดโดยการระบายสีแต่ละจุดด้วยสีของไซต์ที่ใกล้เคียงที่สุด สิ่งนี้เรียกว่าแผนที่ Voronoi (หรือแผนภาพ Voronoi ) โดยหลักการแล้วแผนที่ Voronoi สามารถกำหนดได้สำหรับการวัดระยะทางใด ๆ แต่เราจะใช้ระยะทางแบบยุคลิดแบบr = √(x² + y²)ธรรมดา ( หมายเหตุ:คุณไม่จำเป็นต้องรู้วิธีคำนวณและแสดงผลหนึ่งในสิ่งเหล่านี้เพื่อแข่งขันในการท้าทายนี้)

นี่คือตัวอย่างจาก 100 เว็บไซต์:

ป้อนคำอธิบายรูปภาพที่นี่

หากคุณดูที่เซลล์ใด ๆ คะแนนทั้งหมดในเซลล์นั้นจะอยู่ใกล้กับไซต์ที่เกี่ยวข้องมากกว่าไซต์อื่น ๆ

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

คุณสามารถใช้กองย่อยตัวอย่างที่ด้านล่างของความท้าทายนี้เพื่อแสดงแผนที่ Voronoi จากผลลัพธ์ของคุณหรือคุณสามารถสร้างมันเองถ้าคุณต้องการ

คุณอาจใช้ฟังก์ชันในตัวหรือบุคคลที่สามเพื่อคำนวณแผนที่ Voronoi จากชุดของไซต์ (ถ้าคุณต้องการ)

นี่คือการประกวดความนิยมดังนั้นคำตอบที่ได้คะแนนโหวตมากที่สุดจะเป็นฝ่ายชนะ ผู้มีสิทธิเลือกตั้งได้รับการสนับสนุนให้ตัดสินคำตอบด้วย

  • ภาพต้นฉบับและสีของภาพนั้นดีแค่ไหน
  • อัลกอริทึมทำงานได้ดีกับรูปภาพประเภทต่างๆอย่างไร
  • วิธีการที่ดีขั้นตอนวิธีการทำงานสำหรับธุรกิจขนาดเล็กN
  • อัลกอริทึมจะปรับกลุ่มจุดในภาพที่ต้องการรายละเอียดเพิ่มเติมหรือไม่

ทดสอบภาพ

ต่อไปนี้เป็นภาพสองสามภาพเพื่อทดสอบอัลกอริทึมของคุณ คลิกที่ภาพเพื่อดูขนาดใหญ่ขึ้น

คลื่นลูกใหญ่ เม่น ชายหาด คอร์เนล ดาวเสาร์ หมีสีน้ำตาล โยชิ ลิงแมนดริล เนบิวลาปู Geobits 'Kid น้ำตก กรี๊ด

ชายหาดในแถวแรกถูกดึงดูดโดยOlivia Bellและรวมกับการอนุญาตของเธอ

หากคุณต้องการความท้าทายเป็นพิเศษลองYoshi ด้วยพื้นหลังสีขาวและทำให้หน้าท้องของเขาถูกต้อง

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

โปรดใส่ไดอะแกรมตัวอย่างสำหรับรูปภาพที่แตกต่างกันและNเช่น 100, 300, 1,000, 3000 (รวมถึง Pastebins สำหรับข้อมูลจำเพาะของเซลล์ที่เกี่ยวข้องบางส่วน) คุณอาจใช้หรือไม่ใช้ขอบดำระหว่างเซลล์ตามที่เห็นสมควร อย่ารวมเว็บไซต์ (ยกเว้นในตัวอย่างที่แยกต่างหากบางทีหากคุณต้องการอธิบายวิธีการจัดวางเว็บไซต์ของคุณ)

หากคุณต้องการแสดงผลลัพธ์จำนวนมากคุณสามารถสร้างแกลเลอรี่ได้ที่imgur.comเพื่อให้ขนาดของคำตอบมีความสมเหตุสมผล อีกวิธีหนึ่งคือใส่รูปขนาดย่อในโพสต์ของคุณและทำให้พวกเขาเชื่อมโยงไปยังรูปภาพขนาดใหญ่ที่สุดเช่นผมในคำตอบของการอ้างอิงของฉัน คุณสามารถรับรูปขนาดย่อขนาดเล็กได้โดยต่อท้ายsชื่อไฟล์ในลิงค์ imgur.com (เช่นI3XrT.png-> I3XrTs.png) นอกจากนี้อย่าลังเลที่จะใช้ภาพทดสอบอื่น ๆ หากคุณพบสิ่งที่ดี

renderer

วางผลลัพธ์ของคุณในส่วนย่อยต่อไปนี้เพื่อแสดงผลลัพธ์ของคุณ รูปแบบรายการที่แน่นอนไม่เกี่ยวข้องตราบใดที่แต่ละเซลล์จะถูกระบุโดยหมายเลข 5 จุดที่ลอยอยู่ในการสั่งซื้อx y r g bที่xและyพิกัดของเว็บไซต์ของเซลล์และr g bเป็นสีแดง, 0 ≤ r, g, b ≤ 1สีเขียวและสีฟ้าสีช่องในช่วง

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

สินเชื่อขนาดใหญ่เรย์มอนด์ฮิลล์สำหรับการเขียนห้องสมุด JS Voronoi นี้ดีจริงๆ

ความท้าทายที่เกี่ยวข้อง


5
@frogeyedpeas โดยดูจากคะแนนโหวตที่คุณได้รับ ;) นี่คือการประกวดความนิยม ไม่จำเป็นต้องเป็นวิธีที่ดีที่สุด แนวคิดก็คือคุณพยายามทำมันให้ดีที่สุดเท่าที่จะทำได้และการโหวตจะสะท้อนให้เห็นว่าผู้คนเห็นด้วยว่าคุณทำงานได้ดี มีความเป็นส่วนตัวจำนวนหนึ่งในสิ่งเหล่านี้เป็นที่ยอมรับ มีลักษณะที่ความท้าทายที่เกี่ยวข้องฉันเชื่อมโยงหรือที่หนึ่งนี้ คุณจะเห็นว่ามักจะมีวิธีการที่หลากหลาย แต่ระบบการลงคะแนนจะช่วยแก้ปัญหาได้ดีกว่าและขึ้นอยู่กับการตัดสินใจของผู้ชนะ
Martin Ender

3
โอลิเวียอนุมัติการประมาณค่าของชายหาดที่เธอส่งมา
Alex A.

3
@AlexA เดวอนอนุมัติของบางส่วนของการประมาณใบหน้าของเขาที่ส่งมาป่านนี้ เขาไม่ใช่แฟนตัวยงของ n = 100 เวอร์ชั่นใด ๆ )
Geobits

1
@Geobits: เขาจะเข้าใจเมื่อเขาโตขึ้น
Alex A.

1
นี่คือหน้าเกี่ยวกับ Voronoi ตามเทคนิค แหล่งที่มาของแรงบันดาลใจที่ดี (วิทยานิพนธ์หลักที่เกี่ยวข้องมีการสนทนาที่ดีของการปรับปรุงที่เป็นไปได้กับอัลกอริทึม)
งาน

คำตอบ:


112

Python + scipy + scikit-image , การสุ่มตัวอย่างดิสก์ Poisson ถ่วงน้ำหนัก

โซลูชันของฉันค่อนข้างซับซ้อน ฉันทำการประมวลผลล่วงหน้ากับภาพเพื่อลบจุดรบกวนและรับการแมปว่า 'แต่ละจุด' น่าสนใจ '(ใช้การรวมกันของการตรวจสอบเอนโทรปีและการตรวจจับขอบ):

จากนั้นฉันเลือกจุดสุ่มตัวอย่างโดยใช้การสุ่มดิสก์แบบปัวซองด้วยการบิด: ระยะทางของวงกลมจะถูกกำหนดโดยน้ำหนักที่เรากำหนดไว้ก่อนหน้านี้

จากนั้นเมื่อฉันมีจุดการสุ่มตัวอย่างฉันแบ่งภาพในส่วนของ voronoi และกำหนดค่าเฉลี่ย L * a * b * ของค่าสีภายในแต่ละส่วนให้กับแต่ละส่วน

ฉันมีฮิวริสติกจำนวนมากและฉันต้องทำคณิตศาสตร์นิดหน่อยเพื่อให้แน่ใจว่าจำนวนคะแนนตัวอย่างใกล้เคียงNกัน ฉันได้รับNโดยการแก้ไขปัญหาเล็กน้อยแล้วลดทอนบางประเด็นด้วยฮิวริสติก

ในแง่ของรันไทม์ตัวกรองนี้ไม่ถูกแต่ไม่มีภาพด้านล่างใช้เวลาเกิน 5 วินาทีในการสร้าง

โดยไม่ต้องกังวลใจเพิ่มเติม:

import math
import random
import collections
import os
import sys
import functools
import operator as op
import numpy as np
import warnings

from scipy.spatial import cKDTree as KDTree
from skimage.filters.rank import entropy
from skimage.morphology import disk, dilation
from skimage.util import img_as_ubyte
from skimage.io import imread, imsave
from skimage.color import rgb2gray, rgb2lab, lab2rgb
from skimage.filters import sobel, gaussian_filter
from skimage.restoration import denoise_bilateral
from skimage.transform import downscale_local_mean


# Returns a random real number in half-open range [0, x).
def rand(x):
    r = x
    while r == x:
        r = random.uniform(0, x)
    return r


def poisson_disc(img, n, k=30):
    h, w = img.shape[:2]

    nimg = denoise_bilateral(img, sigma_range=0.15, sigma_spatial=15)
    img_gray = rgb2gray(nimg)
    img_lab = rgb2lab(nimg)

    entropy_weight = 2**(entropy(img_as_ubyte(img_gray), disk(15)))
    entropy_weight /= np.amax(entropy_weight)
    entropy_weight = gaussian_filter(dilation(entropy_weight, disk(15)), 5)

    color = [sobel(img_lab[:, :, channel])**2 for channel in range(1, 3)]
    edge_weight = functools.reduce(op.add, color) ** (1/2) / 75
    edge_weight = dilation(edge_weight, disk(5))

    weight = (0.3*entropy_weight + 0.7*edge_weight)
    weight /= np.mean(weight)
    weight = weight

    max_dist = min(h, w) / 4
    avg_dist = math.sqrt(w * h / (n * math.pi * 0.5) ** (1.05))
    min_dist = avg_dist / 4

    dists = np.clip(avg_dist / weight, min_dist, max_dist)

    def gen_rand_point_around(point):
        radius = random.uniform(dists[point], max_dist)
        angle = rand(2 * math.pi)
        offset = np.array([radius * math.sin(angle), radius * math.cos(angle)])
        return tuple(point + offset)

    def has_neighbours(point):
        point_dist = dists[point]
        distances, idxs = tree.query(point,
                                    len(sample_points) + 1,
                                    distance_upper_bound=max_dist)

        if len(distances) == 0:
            return True

        for dist, idx in zip(distances, idxs):
            if np.isinf(dist):
                break

            if dist < point_dist and dist < dists[tuple(tree.data[idx])]:
                return True

        return False

    # Generate first point randomly.
    first_point = (rand(h), rand(w))
    to_process = [first_point]
    sample_points = [first_point]
    tree = KDTree(sample_points)

    while to_process:
        # Pop a random point.
        point = to_process.pop(random.randrange(len(to_process)))

        for _ in range(k):
            new_point = gen_rand_point_around(point)

            if (0 <= new_point[0] < h and 0 <= new_point[1] < w
                    and not has_neighbours(new_point)):
                to_process.append(new_point)
                sample_points.append(new_point)
                tree = KDTree(sample_points)
                if len(sample_points) % 1000 == 0:
                    print("Generated {} points.".format(len(sample_points)))

    print("Generated {} points.".format(len(sample_points)))

    return sample_points


def sample_colors(img, sample_points, n):
    h, w = img.shape[:2]

    print("Sampling colors...")
    tree = KDTree(np.array(sample_points))
    color_samples = collections.defaultdict(list)
    img_lab = rgb2lab(img)
    xx, yy = np.meshgrid(np.arange(h), np.arange(w))
    pixel_coords = np.c_[xx.ravel(), yy.ravel()]
    nearest = tree.query(pixel_coords)[1]

    i = 0
    for pixel_coord in pixel_coords:
        color_samples[tuple(tree.data[nearest[i]])].append(
            img_lab[tuple(pixel_coord)])
        i += 1

    print("Computing color means...")
    samples = []
    for point, colors in color_samples.items():
        avg_color = np.sum(colors, axis=0) / len(colors)
        samples.append(np.append(point, avg_color))

    if len(samples) > n:
        print("Downsampling {} to {} points...".format(len(samples), n))

    while len(samples) > n:
        tree = KDTree(np.array(samples))
        dists, neighbours = tree.query(np.array(samples), 2)
        dists = dists[:, 1]
        worst_idx = min(range(len(samples)), key=lambda i: dists[i])
        samples[neighbours[worst_idx][1]] += samples[neighbours[worst_idx][0]]
        samples[neighbours[worst_idx][1]] /= 2
        samples.pop(neighbours[worst_idx][0])

    color_samples = []
    for sample in samples:
        color = lab2rgb([[sample[2:]]])[0][0]
        color_samples.append(tuple(sample[:2][::-1]) + tuple(color))

    return color_samples


def render(img, color_samples):
    print("Rendering...")
    h, w = [2*x for x in img.shape[:2]]
    xx, yy = np.meshgrid(np.arange(h), np.arange(w))
    pixel_coords = np.c_[xx.ravel(), yy.ravel()]

    colors = np.empty([h, w, 3])
    coords = []
    for color_sample in color_samples:
        coord = tuple(x*2 for x in color_sample[:2][::-1])
        colors[coord] = color_sample[2:]
        coords.append(coord)

    tree = KDTree(coords)
    idxs = tree.query(pixel_coords)[1]
    data = colors[tuple(tree.data[idxs].astype(int).T)].reshape((w, h, 3))
    data = np.transpose(data, (1, 0, 2))

    return downscale_local_mean(data, (2, 2, 1))


if __name__ == "__main__":
    warnings.simplefilter("ignore")

    img = imread(sys.argv[1])[:, :, :3]

    print("Calibrating...")
    mult = 1.02 * 500 / len(poisson_disc(img, 500))

    for n in (100, 300, 1000, 3000):
        print("Sampling {} for size {}.".format(sys.argv[1], n))

        sample_points = poisson_disc(img, mult * n)
        samples = sample_colors(img, sample_points, n)
        base = os.path.basename(sys.argv[1])
        with open("{}-{}.txt".format(os.path.splitext(base)[0], n), "w") as f:
            for sample in samples:
                f.write(" ".join("{:.3f}".format(x) for x in sample) + "\n")

        imsave("autorenders/{}-{}.png".format(os.path.splitext(base)[0], n),
            render(img, samples))

        print("Done!")

ภาพ

ตามลำดับNคือ 100, 300, 1,000 และ 3000:

abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc
abc abc abc abc


2
ฉันชอบสิ่งนี้; มันดูคล้ายกับกระจกรมควัน
BobTheAwesome

3
ฉันยุ่งกับสิ่งนี้เล็กน้อยและคุณจะได้ผลลัพธ์ที่ดีขึ้นโดยเฉพาะกับรูปสามเหลี่ยมต่ำหากคุณแทนที่ denoise_bilatteral ด้วย denoise_tv_bregman มันสร้างแพทช์ได้มากขึ้นใน denoising ซึ่งช่วย
LKlevin

@LKlevin คุณใช้น้ำหนักเท่าไหร่?
orlp

ฉันใช้ 1.0 เป็นน้ำหนัก
LKlevin

65

C ++

วิธีการของฉันค่อนข้างช้า แต่ฉันมีความสุขมากกับคุณภาพของผลลัพธ์ที่ได้รับโดยเฉพาะอย่างยิ่งสำหรับการรักษาขอบ ตัวอย่างเช่นนี่คือYoshiและCornell Box ที่มีไซต์เพียง 1,000 แห่งเท่านั้น:

มีสองส่วนหลักที่ทำให้ติ๊ก ประการแรกที่รวบรวมไว้ในevaluate()ฟังก์ชั่นใช้ชุดของสถานที่ตั้งของผู้สมัครตั้งค่าสีที่เหมาะสมกับพวกเขาและส่งกลับคะแนนสำหรับPSNRของ Voronoi tessellation ที่แสดงผลเทียบกับภาพเป้าหมาย สีสำหรับแต่ละไซต์นั้นพิจารณาจากค่าเฉลี่ยของพิกเซลภาพเป้าหมายที่ครอบคลุมโดยเซลล์รอบ ๆ ไซต์ ฉันใช้อัลกอริทึมของ Welfordเพื่อช่วยคำนวณทั้งสีที่ดีที่สุดสำหรับแต่ละเซลล์และ PSNR ที่เป็นผลลัพธ์โดยใช้การส่งผ่านภาพเพียงครั้งเดียวโดยใช้ประโยชน์จากความสัมพันธ์ระหว่างความแปรปรวน, MSE และ PSNR สิ่งนี้จะช่วยลดปัญหาให้กับหนึ่งในการค้นหาชุดตำแหน่งที่ตั้งที่ดีที่สุดโดยไม่คำนึงถึงสี

ส่วนที่สองเป็นตัวเป็นตนในmain()พยายามหาชุดนี้ มันเริ่มต้นด้วยการเลือกชุดของคะแนนโดยการสุ่ม จากนั้นในแต่ละขั้นตอนจะลบจุดหนึ่ง (ไปรอบ ๆ โรบิน) และทดสอบชุดของคะแนนผู้สมัครแบบสุ่มเพื่อแทนที่ คนที่ให้ค่า PSNR สูงสุดของกลุ่มได้รับการยอมรับและเก็บรักษาไว้ อย่างมีประสิทธิภาพสิ่งนี้ทำให้ไซต์ข้ามไปยังตำแหน่งใหม่และโดยทั่วไปแล้วจะปรับปรุงรูปภาพทีละบิต โปรดทราบว่าอัลกอริทึมโดยเจตนาไม่ได้รักษาตำแหน่งดั้งเดิมไว้เป็นผู้สมัคร บางครั้งนั่นหมายความว่าการกระโดดจะลดคุณภาพของภาพโดยรวมลง การอนุญาตให้สิ่งนี้เกิดขึ้นจะช่วยหลีกเลี่ยงการติดขัดใน Maxima ท้องถิ่น นอกจากนี้ยังให้เกณฑ์การหยุด; โปรแกรมจะหยุดทำงานหลังจากผ่านไปหลายขั้นตอนโดยไม่ต้องปรับปรุงเว็บไซต์ที่ดีที่สุดเท่าที่พบ

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

รหัส

#include <cstdlib>
#include <cmath>
#include <string>
#include <vector>
#include <fstream>
#include <istream>
#include <ostream>
#include <iostream>
#include <algorithm>
#include <random>

static auto const decimation = 2;
static auto const candidates = 96;
static auto const termination = 200;

using namespace std;

struct rgb {float red, green, blue;};
struct img {int width, height; vector<rgb> pixels;};
struct site {float x, y; rgb color;};

img read(string const &name) {
    ifstream file{name, ios::in | ios::binary};
    auto result = img{0, 0, {}};
    if (file.get() != 'P' || file.get() != '6')
        return result;
    auto skip = [&](){
        while (file.peek() < '0' || '9' < file.peek())
            if (file.get() == '#')
                while (file.peek() != '\r' && file.peek() != '\n')
                    file.get();
    };
     auto maximum = 0;
     skip(); file >> result.width;
     skip(); file >> result.height;
     skip(); file >> maximum;
     file.get();
     for (auto pixel = 0; pixel < result.width * result.height; ++pixel) {
         auto red = file.get() * 1.0f / maximum;
         auto green = file.get() * 1.0f / maximum;
         auto blue = file.get() * 1.0f / maximum;
         result.pixels.emplace_back(rgb{red, green, blue});
     }
     return result;
 }

 float evaluate(img const &target, vector<site> &sites) {
     auto counts = vector<int>(sites.size());
     auto variance = vector<rgb>(sites.size());
     for (auto &site : sites)
         site.color = rgb{0.0f, 0.0f, 0.0f};
     for (auto y = 0; y < target.height; y += decimation)
         for (auto x = 0; x < target.width; x += decimation) {
             auto best = 0;
             auto closest = 1.0e30f;
             for (auto index = 0; index < sites.size(); ++index) {
                 float distance = ((x - sites[index].x) * (x - sites[index].x) +
                                   (y - sites[index].y) * (y - sites[index].y));
                 if (distance < closest) {
                     best = index;
                     closest = distance;
                 }
             }
             ++counts[best];
             auto &pixel = target.pixels[y * target.width + x];
             auto &color = sites[best].color;
             rgb delta = {pixel.red - color.red,
                          pixel.green - color.green,
                          pixel.blue - color.blue};
             color.red += delta.red / counts[best];
             color.green += delta.green / counts[best];
             color.blue += delta.blue / counts[best];
             variance[best].red += delta.red * (pixel.red - color.red);
             variance[best].green += delta.green * (pixel.green - color.green);
             variance[best].blue += delta.blue * (pixel.blue - color.blue);
         }
     auto error = 0.0f;
     auto count = 0;
     for (auto index = 0; index < sites.size(); ++index) {
         if (!counts[index]) {
             auto x = min(max(static_cast<int>(sites[index].x), 0), target.width - 1);
             auto y = min(max(static_cast<int>(sites[index].y), 0), target.height - 1);
             sites[index].color = target.pixels[y * target.width + x];
         }
         count += counts[index];
         error += variance[index].red + variance[index].green + variance[index].blue;
     }
     return 10.0f * log10f(count * 3 / error);
 }

 void write(string const &name, int const width, int const height, vector<site> const &sites) {
     ofstream file{name, ios::out};
     file << width << " " << height << endl;
     for (auto const &site : sites)
         file << site.x << " " << site.y << " "
              << site.color.red << " "<< site.color.green << " "<< site.color.blue << endl;
 }

 int main(int argc, char **argv) {
     auto rng = mt19937{random_device{}()};
     auto uniform = uniform_real_distribution<float>{0.0f, 1.0f};
     auto target = read(argv[1]);
     auto sites = vector<site>{};
     for (auto point = atoi(argv[2]); point; --point)
         sites.emplace_back(site{
             target.width * uniform(rng),
             target.height * uniform(rng)});
     auto greatest = 0.0f;
     auto remaining = termination;
     for (auto step = 0; remaining; ++step, --remaining) {
         auto best_candidate = sites;
         auto best_psnr = 0.0f;
         #pragma omp parallel for
         for (auto candidate = 0; candidate < candidates; ++candidate) {
             auto trial = sites;
             #pragma omp critical
             {
                 trial[step % sites.size()].x = target.width * (uniform(rng) * 1.2f - 0.1f);
                 trial[step % sites.size()].y = target.height * (uniform(rng) * 1.2f - 0.1f);
             }
             auto psnr = evaluate(target, trial);
             #pragma omp critical
             if (psnr > best_psnr) {
                 best_candidate = trial;
                 best_psnr = psnr;
             }
         }
         sites = best_candidate;
         if (best_psnr > greatest) {
             greatest = best_psnr;
             remaining = termination;
             write(argv[3], target.width, target.height, sites);
         }
         cout << "Step " << step << "/" << remaining
              << ", PSNR = " << best_psnr << endl;
     }
     return 0;
 }

วิ่ง

โปรแกรมมีอยู่ในตัวเองและไม่มีการพึ่งพาภายนอกเกินกว่ามาตรฐานไลบรารี แต่มันต้องการรูปภาพที่จะอยู่ในรูปแบบไบนารี PPM ฉันใช้ImageMagickเพื่อแปลงรูปภาพเป็น PPM แม้ว่า GIMP และโปรแกรมอื่น ๆ อีกสองสามคนก็สามารถทำได้เช่นกัน

หากต้องการคอมไพล์ให้บันทึกโปรแกรมเป็นvoronoi.cppแล้วเรียกใช้:

g++ -std=c++11 -fopenmp -O3 -o voronoi voronoi.cpp

ฉันคาดว่าอาจใช้งานได้กับ Windows ด้วย Visual Studio รุ่นล่าสุดแม้ว่าฉันจะไม่ได้ลองก็ตาม คุณจะต้องแน่ใจว่าคุณคอมไพล์ด้วย C ++ 11 หรือดีกว่าและเปิดใช้งาน OpenMP ถ้าคุณทำ OpenMP ไม่จำเป็นอย่างเคร่งครัด แต่มันช่วยได้มากในการทำให้เวลาในการประมวลผลนั้นทนทานมากขึ้น

หากต้องการเรียกใช้ให้ทำดังนี้:

./voronoi cornell.ppm 1000 cornell-1000.txt

ไฟล์ในภายหลังจะได้รับการอัปเดตพร้อมกับข้อมูลไซต์ตามที่ไป บรรทัดแรกจะมีความกว้างและความสูงของภาพตามด้วยบรรทัดของค่า x, y, r, g, b ที่เหมาะสมสำหรับการคัดลอกและวางลงใน Javascript renderer ในคำอธิบายปัญหา

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

ภาพ

N = 100, 300, 1000 และ 3000:


1
นี่น่าจะชนะ IMO - ดีกว่าของฉันมาก
orlp

1
@orlp - ขอบคุณ! เพื่อความเป็นธรรมแม้ว่าคุณโพสต์ของคุณเร็วกว่านี้และมันจะทำงานเร็วกว่ามาก ความเร็วนับ!
Boojum

1
ทีนี้ฉันไม่ใช่คำตอบแผนที่ voronoi จริงๆ :) มันเป็นอัลกอริธึมการสุ่มตัวอย่างที่ดีจริง ๆ แต่การเปลี่ยนจุดตัวอย่างเป็นเว็บไซต์ voronoi นั้นไม่เหมาะสม
orlp

55

IDL การปรับแต่งที่ปรับได้

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

ฉันได้ส่งออกบางส่วนของตัวกลางสำหรับการดำภาพพื้นหลังทดสอบ yoshi n = 1000ด้วย

อันดับแรกเราทำการเฉดสีขาวสว่างของภาพ (โดยใช้ct_luminance) และใช้ตัวกรอง Prewitt ( prewittดูวิกิพีเดีย ) สำหรับการตรวจจับขอบที่ดี:

abc abc

จากนั้นก็เป็นงานเสี้ยงฮึดฮัดแสดงความไม่พอใจจริง: เราแบ่งภาพออกเป็น 4 และวัดความแปรปรวนในแต่ละด้านในภาพที่กรอง ความแปรปรวนของเรามีน้ำหนักตามขนาดของแผนก (ซึ่งในขั้นตอนแรกนี้เท่ากัน) ดังนั้นภูมิภาค "หงุดหงิด" ที่มีความแปรปรวนสูงจะไม่ได้รับการแบ่งย่อยให้เล็กลงและเล็กลง จากนั้นเราจะใช้ความแปรปรวนแบบถ่วงน้ำหนักเพื่อกำหนดเป้าหมายส่วนย่อยโดยมีรายละเอียดมากขึ้นและแบ่งย่อยส่วนที่มีรายละเอียดซ้ำ ๆ ออกเป็น 4 ส่วนเพิ่มเติมจนกว่าเราจะไปถึงจำนวนเป้าหมายของไซต์ เนื่องจากเราเพิ่ม 3 เว็บไซต์ในแต่ละครั้งที่เราทำซ้ำเราจึงสิ้นสุดด้วยn - 2 <= N <= nไซต์

ฉันทำ .webm ของกระบวนการจัดสรรสำหรับภาพนี้ซึ่งผมไม่สามารถฝัง แต่มันนี่ ; สีในแต่ละส่วนย่อยถูกกำหนดโดยความแปรปรวนแบบถ่วงน้ำหนัก (ฉันทำวิดีโอประเภทเดียวกันสำหรับโยชิพื้นหลังสีขาวเพื่อเปรียบเทียบโดยที่ตารางสีกลับด้านเพื่อให้เป็นสีขาวแทนที่จะเป็นสีดำมันอยู่ที่นี่ ) ผลิตภัณฑ์ขั้นสุดท้ายของการแบ่งมีลักษณะดังนี้:

abc

เมื่อเรามีรายการเขตการปกครองของเราเราจะผ่านแต่ละเขตการปกครอง ตำแหน่งสุดท้ายของเว็บไซต์คือตำแหน่งของภาพขั้นต่ำของ Prewitt กล่าวคือพิกเซล "หงุดหงิด" น้อยที่สุดและสีของส่วนคือสีของพิกเซลนั้น นี่คือภาพต้นฉบับโดยมีไซต์ที่ทำเครื่องหมายไว้:

abc

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

abc

รหัส:

function subdivide, image, bounds, vars
  ;subdivide a section into 4, and return the 4 subdivisions and the variance of each
  division = list()
  vars = list()
  nx = bounds[2] - bounds[0]
  ny = bounds[3] - bounds[1]
  for i=0,1 do begin
    for j=0,1 do begin
      x = i * nx/2 + bounds[0]
      y = j * ny/2 + bounds[1]
      sub = image[x:x+nx/2-(~(nx mod 2)),y:y+ny/2-(~(ny mod 2))]
      division.add, [x,y,x+nx/2-(~(nx mod 2)),y+ny/2-(~(ny mod 2))]
      vars.add, variance(sub) * n_elements(sub)
    endfor
  endfor
  return, division
end

pro voro_map, n, image, outfile
  sz = size(image, /dim)
  ;first, convert image to greyscale, and then use a Prewitt filter to pick out edges
  edges = prewitt(reform(ct_luminance(image[0,*,*], image[1,*,*], image[2,*,*])))
  ;next, iteratively subdivide the image into sections, using variance to pick
  ;the next subdivision target (variance -> detail) until we've hit N subdivisions
  subdivisions = subdivide(edges, [0,0,sz[1],sz[2]], variances)
  while subdivisions.count() lt (n - 2) do begin
    !null = max(variances.toarray(),target)
    oldsub = subdivisions.remove(target)
    newsub = subdivide(edges, oldsub, vars)
    if subdivisions.count(newsub[0]) gt 0 or subdivisions.count(newsub[1]) gt 0 or subdivisions.count(newsub[2]) gt 0 or subdivisions.count(newsub[3]) gt 0 then stop
    subdivisions += newsub
    variances.remove, target
    variances += vars
  endwhile
  ;now we find the minimum edge value of each subdivision (we want to pick representative 
  ;colors, not edge colors) and use that as the site (with associated color)
  sites = fltarr(2,n)
  colors = lonarr(n)
  foreach sub, subdivisions, i do begin
    slice = edges[sub[0]:sub[2],sub[1]:sub[3]]
    !null = min(slice,target)
    sxy = array_indices(slice, target) + sub[0:1]
    sites[*,i] = sxy
    colors[i] = cgcolor24(image[0:2,sxy[0],sxy[1]])
  endforeach
  ;finally, generate the voronoi map
  old = !d.NAME
  set_plot, 'Z'
  device, set_resolution=sz[1:2], decomposed=1, set_pixel_depth=24
  triangulate, sites[0,*], sites[1,*], tr, connectivity=C
  for i=0,n-1 do begin
    if C[i] eq C[i+1] then continue
    voronoi, sites[0,*], sites[1,*], i, C, xp, yp
    cgpolygon, xp, yp, color=colors[i], /fill, /device
  endfor
  !null = cgsnapshot(file=outfile, /nodialog)
  set_plot, old
end

pro wrapper
  cd, '~/voronoi'
  fs = file_search()
  foreach f,fs do begin
    base = strsplit(f,'.',/extract)
    if base[1] eq 'png' then im = read_png(f) else read_jpeg, f, im
    voro_map,100, im, base[0]+'100.png'
    voro_map,500, im, base[0]+'500.png'
    voro_map,1000,im, base[0]+'1000.png'
  endforeach
end

โทรหาทางvoro_map, n, image, output_filenameนี้ ฉันรวมwrapperขั้นตอนด้วยเช่นกันซึ่งผ่านแต่ละภาพทดสอบและวิ่งไปที่ไซต์ 100, 500 และ 1,000

รวบรวมผลลัพธ์ที่นี่และนี่คือรูปขนาดย่อบางส่วน:

n = 100

abc abc abc abc abc abc abc abc abc abc abc abc abc

n = 500

abc abc abc abc abc abc abc abc abc abc abc abc abc

n = 1000

abc abc abc abc abc abc abc abc abc abc abc abc abc


9
ฉันชอบความจริงที่ว่าวิธีการแก้ปัญหานี้ทำให้มีจุดมากขึ้นในพื้นที่ที่ซับซ้อนมากขึ้นซึ่งฉันคิดว่าความตั้งใจและแยกมันออกจากจุดอื่น ๆ
alexander-brett

ใช่ความคิดของคะแนนรายละเอียดจัดกลุ่มคือสิ่งที่ทำให้ฉันไปปรับแต่งการปรับแต่ง
sirpercival

3
คำอธิบายที่ประณีตมากและภาพก็น่าประทับใจ! ฉันมีคำถาม - ดูเหมือนว่าคุณจะได้ภาพที่แตกต่างกันมากเมื่อโยชิอยู่บนพื้นหลังสีขาวที่เรามีรูปร่างแปลก ๆ สิ่งใดที่อาจเป็นสาเหตุให้เกิดขึ้น?
BrainSteel

2
@BrianSteel ผมคิดว่าเค้าร่างได้รับเลือกขึ้นเป็นพื้นที่สูงแปรปรวนและมุ่งเน้นไปที่ไม่จำเป็นแล้วอื่น ๆอย่างแท้จริงพื้นที่รายละเอียดสูงมีจุดน้อยที่ได้รับมอบหมายเนื่องจากการที่
doppelgreener

@BrainSteel ฉันคิดว่า doppel ถูกต้อง - มีความแข็งแกร่งระหว่างขอบสีดำกับพื้นหลังสีขาวซึ่งขอรายละเอียดจำนวนมากในอัลกอริทึม ฉันไม่แน่ใจว่านี่เป็นสิ่งที่ฉันสามารถทำได้ (หรือที่สำคัญกว่านั้นควร ) แก้ไข ...
45790

47

Python 3 + PIL + SciPy, K-mean

from collections import defaultdict
import itertools
import random
import time

from PIL import Image
import numpy as np
from scipy.spatial import KDTree, Delaunay

INFILE = "planet.jpg"
OUTFILE = "voronoi.txt"
N = 3000

DEBUG = True # Outputs extra images to see what's happening
FEATURE_FILE = "features.png"
SAMPLE_FILE = "samples.png"
SAMPLE_POINTS = 20000
ITERATIONS = 10
CLOSE_COLOR_THRESHOLD = 15

"""
Color conversion functions
"""

start_time = time.time()

# http://www.easyrgb.com/?X=MATH
def rgb2xyz(rgb):
  r, g, b = rgb
  r /= 255
  g /= 255
  b /= 255

  r = ((r + 0.055)/1.055)**2.4 if r > 0.04045 else r/12.92
  g = ((g + 0.055)/1.055)**2.4 if g > 0.04045 else g/12.92
  b = ((b + 0.055)/1.055)**2.4 if b > 0.04045 else b/12.92

  r *= 100
  g *= 100
  b *= 100

  x = r*0.4124 + g*0.3576 + b*0.1805
  y = r*0.2126 + g*0.7152 + b*0.0722
  z = r*0.0193 + g*0.1192 + b*0.9505

  return (x, y, z)

def xyz2lab(xyz):
  x, y, z = xyz
  x /= 95.047
  y /= 100
  z /= 108.883

  x = x**(1/3) if x > 0.008856 else 7.787*x + 16/116
  y = y**(1/3) if y > 0.008856 else 7.787*y + 16/116
  z = z**(1/3) if z > 0.008856 else 7.787*z + 16/116

  L = 116*y - 16
  a = 500*(x - y)
  b = 200*(y - z)

  return (L, a, b)

def rgb2lab(rgb):
  return xyz2lab(rgb2xyz(rgb))

def lab2xyz(lab):
  L, a, b = lab
  y = (L + 16)/116
  x = a/500 + y
  z = y - b/200

  y = y**3 if y**3 > 0.008856 else (y - 16/116)/7.787
  x = x**3 if x**3 > 0.008856 else (x - 16/116)/7.787
  z = z**3 if z**3 > 0.008856 else (z - 16/116)/7.787

  x *= 95.047
  y *= 100
  z *= 108.883

  return (x, y, z)

def xyz2rgb(xyz):
  x, y, z = xyz
  x /= 100
  y /= 100
  z /= 100

  r = x* 3.2406 + y*-1.5372 + z*-0.4986
  g = x*-0.9689 + y* 1.8758 + z* 0.0415
  b = x* 0.0557 + y*-0.2040 + z* 1.0570

  r = 1.055 * (r**(1/2.4)) - 0.055 if r > 0.0031308 else 12.92*r
  g = 1.055 * (g**(1/2.4)) - 0.055 if g > 0.0031308 else 12.92*g
  b = 1.055 * (b**(1/2.4)) - 0.055 if b > 0.0031308 else 12.92*b

  r *= 255
  g *= 255
  b *= 255

  return (r, g, b)

def lab2rgb(lab):
  return xyz2rgb(lab2xyz(lab))

"""
Step 1: Read image and convert to CIELAB
"""

im = Image.open(INFILE)
im = im.convert("RGB")
width, height = prev_size = im.size

pixlab_map = {}

for x in range(width):
    for y in range(height):
        pixlab_map[(x, y)] = rgb2lab(im.getpixel((x, y)))

print("Step 1: Image read and converted")

"""
Step 2: Get feature points
"""

def euclidean(point1, point2):
    return sum((x-y)**2 for x,y in zip(point1, point2))**.5


def neighbours(pixel):
    x, y = pixel
    results = []

    for dx, dy in itertools.product([-1, 0, 1], repeat=2):
        neighbour = (pixel[0] + dx, pixel[1] + dy)

        if (neighbour != pixel and 0 <= neighbour[0] < width
            and 0 <= neighbour[1] < height):
            results.append(neighbour)

    return results

def mse(colors, base):
    return sum(euclidean(x, base)**2 for x in colors)/len(colors)

features = []

for x in range(width):
    for y in range(height):
        pixel = (x, y)
        col = pixlab_map[pixel]
        features.append((mse([pixlab_map[n] for n in neighbours(pixel)], col),
                         random.random(),
                         pixel))

features.sort()
features_copy = [x[2] for x in features]

if DEBUG:
    test_im = Image.new("RGB", im.size)

    for i in range(len(features)):
        pixel = features[i][1]
        test_im.putpixel(pixel, (int(255*i/len(features)),)*3)

    test_im.save(FEATURE_FILE)

print("Step 2a: Edge detection-ish complete")

def random_index(list_):
    r = random.expovariate(2)

    while r > 1:
         r = random.expovariate(2)

    return int((1 - r) * len(list_))

sample_points = set()

while features and len(sample_points) < SAMPLE_POINTS:
    index = random_index(features)
    point = features[index][2]
    sample_points.add(point)
    del features[index]

print("Step 2b: {} feature samples generated".format(len(sample_points)))

if DEBUG:
    test_im = Image.new("RGB", im.size)

    for pixel in sample_points:
        test_im.putpixel(pixel, (255, 255, 255))

    test_im.save(SAMPLE_FILE)

"""
Step 3: Fuzzy k-means
"""

def euclidean(point1, point2):
    return sum((x-y)**2 for x,y in zip(point1, point2))**.5

def get_centroid(points):
    return tuple(sum(coord)/len(points) for coord in zip(*points))

def mean_cell_color(cell):
    return get_centroid([pixlab_map[pixel] for pixel in cell])

def median_cell_color(cell):
    # Pick start point out of mean and up to 10 pixels in cell
    mean_col = get_centroid([pixlab_map[pixel] for pixel in cell])
    start_choices = [pixlab_map[pixel] for pixel in cell]

    if len(start_choices) > 10:
        start_choices = random.sample(start_choices, 10)

    start_choices.append(mean_col)

    best_dist = None
    col = None

    for c in start_choices:
        dist = sum(euclidean(c, pixlab_map[pixel])
                       for pixel in cell)

        if col is None or dist < best_dist:
            col = c
            best_dist = dist

    # Approximate median by hill climbing
    last = None

    while last is None or euclidean(col, last) < 1e-6:
        last = col

        best_dist = None
        best_col = None

        for deviation in itertools.product([-1, 0, 1], repeat=3):
            new_col = tuple(x+y for x,y in zip(col, deviation))
            dist = sum(euclidean(new_col, pixlab_map[pixel])
                       for pixel in cell)

            if best_dist is None or dist < best_dist:
                best_col = new_col

        col = best_col

    return col

def random_point():
    index = random_index(features_copy)
    point = features_copy[index]

    dx = random.random() * 10 - 5
    dy = random.random() * 10 - 5

    return (point[0] + dx, point[1] + dy)

centroids = np.asarray([random_point() for _ in range(N)])
variance = {i:float("inf") for i in range(N)}
cluster_colors = {i:(0, 0, 0) for i in range(N)}

# Initial iteration
tree = KDTree(centroids)
clusters = defaultdict(set)

for point in sample_points:
    nearest = tree.query(point)[1]
    clusters[nearest].add(point)

# Cluster!
for iter_num in range(ITERATIONS):
    if DEBUG:
        test_im = Image.new("RGB", im.size)

        for n, pixels in clusters.items():
            color = 0xFFFFFF * (n/N)
            color = (int(color//256//256%256), int(color//256%256), int(color%256))

            for p in pixels:
                test_im.putpixel(p, color)

        test_im.save(SAMPLE_FILE)

    for cluster_num in clusters:
        if clusters[cluster_num]:
            cols = [pixlab_map[x] for x in clusters[cluster_num]]

            cluster_colors[cluster_num] = mean_cell_color(clusters[cluster_num])
            variance[cluster_num] = mse(cols, cluster_colors[cluster_num])

        else:
            cluster_colors[cluster_num] = (0, 0, 0)
            variance[cluster_num] = float("inf")

    print("Clustering (iteration {})".format(iter_num))

    # Remove useless/high variance
    if iter_num < ITERATIONS - 1:
        delaunay = Delaunay(np.asarray(centroids))
        neighbours = defaultdict(set)

        for simplex in delaunay.simplices:
            n1, n2, n3 = simplex

            neighbours[n1] |= {n2, n3}
            neighbours[n2] |= {n1, n3}
            neighbours[n3] |= {n1, n2}

        for num, centroid in enumerate(centroids):
            col = cluster_colors[num]

            like_neighbours = True

            nns = set() # neighbours + neighbours of neighbours

            for n in neighbours[num]:
                nns |= {n} | neighbours[n] - {num}

            nn_far = sum(euclidean(col, cluster_colors[nn]) > CLOSE_COLOR_THRESHOLD
                         for nn in nns)

            if nns and nn_far / len(nns) < 1/5:
                sample_points -= clusters[num]

                for _ in clusters[num]:
                    if features and len(sample_points) < SAMPLE_POINTS:
                        index = random_index(features)
                        point = features[index][3]
                        sample_points.add(point)
                        del features[index]

                clusters[num] = set()

    new_centroids = []

    for i in range(N):
        if clusters[i]:
            new_centroids.append(get_centroid(clusters[i]))
        else:
            new_centroids.append(random_point())

    centroids = np.asarray(new_centroids)
    tree = KDTree(centroids)

    clusters = defaultdict(set)

    for point in sample_points:
        nearest = tree.query(point, k=6)[1]
        col = pixlab_map[point]

        for n in nearest:
            if n < N and euclidean(col, cluster_colors[n])**2 <= variance[n]:
                clusters[n].add(point)
                break

        else:
            clusters[nearest[0]].add(point)

print("Step 3: Fuzzy k-means complete")

"""
Step 4: Output
"""

for i in range(N):
    if clusters[i]:
        centroids[i] = get_centroid(clusters[i])

centroids = np.asarray(centroids)
tree = KDTree(centroids)
color_clusters = defaultdict(set)

# Throw back on some sample points to get the colors right
all_points = [(x, y) for x in range(width) for y in range(height)]

for pixel in random.sample(all_points, int(min(width*height, 5 * SAMPLE_POINTS))):
    nearest = tree.query(pixel)[1]
    color_clusters[nearest].add(pixel)

with open(OUTFILE, "w") as outfile:
    for i in range(N):
        if clusters[i]:
            centroid = tuple(centroids[i])          
            col = tuple(x/255 for x in lab2rgb(median_cell_color(color_clusters[i] or clusters[i])))
            print(" ".join(map(str, centroid + col)), file=outfile)

print("Done! Time taken:", time.time() - start_time)

อัลกอริทึม

แนวคิดหลักคือการจัดกลุ่ม k- หมายถึงการแบ่งกลุ่มภาพในเซลล์ Voronoi อย่างเป็นธรรมชาติเนื่องจากจุดต่างๆนั้นเชื่อมโยงกับเซนทรอยด์ที่ใกล้ที่สุด อย่างไรก็ตามเราจำเป็นต้องเพิ่มสีเป็นข้อ จำกัด

ก่อนอื่นเราจะแปลงแต่ละพิกเซลเป็นพื้นที่สี Labเพื่อการจัดการสีที่ดีขึ้น

จากนั้นเราทำการเรียงลำดับของ "การตรวจจับขอบคนจน" สำหรับแต่ละพิกเซลเราจะดูที่มุมฉากและเส้นทแยงมุมของเพื่อนบ้านและคำนวณความแตกต่างของค่าเฉลี่ยกำลังสองของสี จากนั้นเราจัดเรียงพิกเซลทั้งหมดตามความแตกต่างนี้โดยมีพิกเซลคล้ายกับเพื่อนบ้านของพวกเขามากที่สุดที่ด้านหน้าของรายการและพิกเซลที่แตกต่างกันมากที่สุดกับเพื่อนบ้านของพวกเขาที่ด้านหลัง (เช่นมีแนวโน้มที่จะเป็นจุดขอบ) นี่คือตัวอย่างสำหรับดาวเคราะห์ที่มีความสว่างของพิกเซลมากความแตกต่างก็มากจากเพื่อนบ้าน:

ป้อนคำอธิบายรูปภาพที่นี่

(มีรูปแบบคล้ายกริดที่ชัดเจนในเอาต์พุตที่แสดงผลข้างต้นตาม @ randomra อาจเป็นเพราะการเข้ารหัส JPG ที่สูญหายหรือ imgur บีบอัดภาพ)

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

ป้อนคำอธิบายรูปภาพที่นี่

สำหรับการจัดกลุ่มเราเลือกNเซนทรอยด์ก่อนโดยสุ่มเลือกโดยใช้การแจกแจงเอ็กซ์โปเนนเชียลแบบเดียวกับข้างบน การทำซ้ำเริ่มต้นจะดำเนินการและสำหรับแต่ละกลุ่มผลลัพธ์เรากำหนดสีค่าเฉลี่ยและเกณฑ์ผลต่างสี จากนั้นเป็นจำนวนซ้ำเรา:

  • สร้าง Delaunay triangulation ของ centroids เพื่อให้เราสามารถสอบถามเพื่อนบ้านกับ centroids ได้อย่างง่ายดาย
  • ใช้สมการรูปสามเหลี่ยมเพื่อกำจัดเซนทรอยด์ที่มีสีใกล้เคียงที่สุด (> 4/5) ของเพื่อนบ้านและเพื่อนบ้านของเพื่อนบ้านรวมกัน จุดตัวอย่างที่เกี่ยวข้องใด ๆ จะถูกลบออกและเพิ่ม centroids และจุดเปลี่ยนใหม่ที่เพิ่มเข้ามา ขั้นตอนนี้พยายามบังคับให้อัลกอริทึมวางกลุ่มเพิ่มเติมที่ต้องการรายละเอียด
  • สร้าง kd-tree สำหรับ centroid ใหม่เพื่อให้เราสามารถค้นหา centroid ที่ใกล้เคียงกับจุดตัวอย่างได้อย่างง่ายดาย
  • ใช้ต้นไม้เพื่อกำหนดจุดแต่ละตัวอย่างให้กับหนึ่งใน 6 เซนทรอยด์ที่ใกล้เคียงที่สุด (6 ที่เลือกสังเกตุ) เซนทรอยด์จะยอมรับเฉพาะจุดตัวอย่างถ้าสีของจุดนั้นอยู่ในเกณฑ์ความแปรปรวนสีของเซนทรอยด์ เราพยายามที่จะกำหนดแต่ละตัวอย่างชี้ไปที่เซนทรอยด์แรกที่ยอมรับ แต่ถ้ามันเป็นไปไม่ได้ "ความคลุมเครือ" ของอัลกอริทึมมาจากขั้นตอนนี้เนื่องจากเป็นไปได้ที่กลุ่มจะทับซ้อนกัน
  • คำนวณ centroids ใหม่

ป้อนคำอธิบายรูปภาพที่นี่

(คลิกเพื่อดูขนาดเต็ม)

สุดท้ายเราสุ่มตัวอย่างเป็นจำนวนมากในครั้งนี้โดยใช้การแจกแจงแบบสม่ำเสมอ ใช้ kd-tree อีกอันเรากำหนดแต่ละจุดให้กับ centroid ที่ใกล้เคียงที่สุด จากนั้นเราจะประมาณค่ามัธยฐานของแต่ละกลุ่มโดยใช้วิธีการปีนเขาให้สีเซลล์สุดท้ายของเรา (ความคิดสำหรับขั้นตอนนี้ต้องขอบคุณ @PhiNotPi และ @ MartinBüttner)

ป้อนคำอธิบายรูปภาพที่นี่

หมายเหตุ

นอกเหนือจากการส่งออกไฟล์ข้อความสำหรับตัวอย่าง ( OUTFILE) หากDEBUGตั้งค่าTrueเป็นโปรแกรมก็จะส่งออกและเขียนทับภาพด้านบน อัลกอริทึมใช้เวลาสองสามนาทีสำหรับแต่ละภาพดังนั้นจึงเป็นวิธีที่ดีในการตรวจสอบความคืบหน้าซึ่งจะไม่เพิ่มความน่ากลัวให้กับเวลาทำงาน

ตัวอย่างผลลัพธ์

N = 32:

ป้อนคำอธิบายรูปภาพที่นี่

N = 100:

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

N = 1,000:

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

N = 3000:

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่


1
ฉันชอบโยชิสสีขาวของคุณออกมาดีแค่ไหน
Max

26

Mathematica เซลล์สุ่ม

นี่คือวิธีการแก้ปัญหาพื้นฐานดังนั้นคุณจะได้รับแนวคิดขั้นต่ำที่ฉันขอจากคุณ กำหนดชื่อไฟล์ (ในเครื่องหรือเป็น URL) ในfileและNในnรหัสต่อไปนี้จะเลือกNสุ่มพิกเซลและใช้สีที่พบที่พิกเซลเหล่านั้น นี่มันไร้เดียงสาจริงๆและทำงานได้ไม่ดีอย่างไม่น่าเชื่อ แต่ฉันต้องการให้พวกคุณเอาชนะมันได้ :)

data = ImageData@Import@file;
dims = Dimensions[data][[1 ;; 2]]
{Reverse@#, data[[##]][[1 ;; 3]] & @@ Floor[1 + #]} &[dims #] & /@ 
 RandomReal[1, {n, 2}]

นี่คือภาพทดสอบทั้งหมดสำหรับN = 100 (ภาพทั้งหมดลิงก์ไปยังรุ่นที่ใหญ่กว่า):

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

อย่างที่คุณเห็นสิ่งเหล่านี้ไร้ประโยชน์เป็นหลัก ในขณะที่พวกเขาอาจมีคุณค่าทางศิลปะในลักษณะที่เป็นแบบนิสต์นิสต์

สำหรับN = 500สถานการณ์ได้รับการปรับปรุงเล็กน้อย แต่ยังคงมีสิ่งประดิษฐ์แปลกมากภาพที่ดูจะถูกชะล้างและเซลล์จำนวนมากถูกทำลายในภูมิภาคโดยไม่มีรายละเอียด:

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

ตาคุณ!


ฉันไม่ได้เป็น coder ที่ดี แต่ภาพเหล่านี้ดูดี ความคิดที่ยอดเยี่ยม!
Faraz Masroor

มีเหตุผลอะไรหรือDimensions@ImageDataไม่ImageDimensions? คุณสามารถหลีกเลี่ยงช้าโดยสิ้นเชิงโดยใช้ImageData PixelValue
2012rcampion

@ 2012rcampion ไม่มีเหตุผลฉันแค่ไม่รู้ว่ามีฟังก์ชันใดอยู่ ฉันอาจแก้ไขมันในภายหลังและเปลี่ยนภาพตัวอย่างเป็นค่า N ที่แนะนำ
Martin Ender

23

มาติกา

เราทุกคนรู้ว่ามาร์ตินชอบมาเธมาติกาดังนั้นให้ฉันลองกับแมททีมาติกา

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

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

ขั้นตอนวิธีการนี้โดยไม่ต้องสุ่มแบบที่สามารถพบได้ในVoronoiMeshเอกสารที่นี่

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

ภาพทดสอบ (100,300,1000,3000)

รหัส

VoronoiImage[img_, nSeeds_, iterations_] := Module[{
    i = img,
    edges = EdgeDetect@img,
    voronoiRegion = Transpose[{{0, 0}, ImageDimensions[img]}],
    seeds, voronoiInitial, voronoiRelaxed
    },
   seeds = RandomChoice[ImageValuePositions[edges, White], nSeeds];
   voronoiInitial = VoronoiMesh[seeds, voronoiRegion];
   voronoiRelaxed = 
    Nest[VoronoiMesh[Mean @@@ MeshPrimitives[#, 2], voronoiRegion] &, 
     voronoiInitial, iterations];
   Graphics[Table[{RGBColor[ImageValue[img, Mean @@ mp]], mp}, 
     {mp,MeshPrimitives[voronoiRelaxed, 2]}]]
   ];

ทำได้ดีมากสำหรับการโพสต์แรก! :) คุณอาจต้องการลองทดสอบภาพ Voronoi ด้วย 32 เซลล์ (นั่นคือจำนวนเซลล์ในภาพ)
Martin Ender

ขอบคุณ! ฉันเดาว่าอัลกอริทึมของฉันจะทำงานได้ดีมากในตัวอย่างนี้ เมล็ดจะได้รับการกำหนดค่าเริ่มต้นบนขอบเซลล์และการเรียกซ้ำจะไม่ทำให้ดีขึ้นมาก)
อุ้งเท้า

แม้อัตราการบรรจบกันของภาพต้นฉบับจะช้าลง แต่ฉันพบว่าอัลกอริทึมของคุณให้ผลลัพธ์ที่ยอดเยี่ยม! (เช่นงานปรับปรุงรุ่นของ Georges Seurat) เยี่ยมมาก!
neizod

นอกจากนี้คุณยังสามารถดูสีโพลีกอนที่เหลือบเหลือบมองโดยการเปลี่ยนบรรทัดสุดท้ายเป็นGraphics@Table[ Append[mp, VertexColors -> RGBColor /@ ImageValue[img, First[mp]]], {mp, MeshPrimitives[voronoiRelaxed, 2]}]
ฮิสโทแกรม

13

Python + SciPy + พิธีกร

อัลกอริทึมที่ฉันใช้มีดังต่อไปนี้:

  1. ปรับขนาดภาพให้มีขนาดเล็ก (~ 150 พิกเซล)
  2. จัดทำภาพที่ไม่ได้บังหน้าของค่าแชนเนลสูงสุด (ซึ่งจะช่วยไม่ให้มีพื้นที่สีขาวแรงเกินไป)
  3. รับค่าสัมบูรณ์
  4. เลือกจุดสุ่มที่มีความน่าจะเป็นเป็นสัดส่วนกับภาพนี้ นี่เป็นการเลือกจุดที่ไม่ต่อเนื่องทั้งสองด้าน
  5. ปรับแต่งคะแนนที่เลือกเพื่อลดฟังก์ชั่นค่าใช้จ่าย ฟังก์ชั่นนี้คือผลรวมสูงสุดของส่วนเบี่ยงเบนกำลังสองในช่องสัญญาณ (ช่วยให้มีอคติกับสีทึบและไม่เพียง แต่สีขาวทึบ) ฉันใช้มาร์คอฟเชนมอนติคาร์โลในทางที่ผิดกับโมดุลย์ผู้แนะนำ (ขอแนะนำ) เป็นเครื่องมือเพิ่มประสิทธิภาพ โพรซีเดอร์ bail out เมื่อไม่พบการปรับปรุงใหม่หลังจากทำซ้ำเชน N

อัลกอริทึมดูเหมือนจะทำงานได้ดีมาก น่าเสียดายที่มันสามารถทำงานได้กับรูปภาพขนาดเล็กเท่านั้น ฉันไม่ได้มีเวลาที่จะใช้คะแนน Voronoi และนำไปใช้กับภาพขนาดใหญ่ พวกเขาสามารถปรับปรุงในจุดนี้ ฉันสามารถใช้งาน MCMC ได้อีกต่อไปเพื่อให้ได้ความเร็วที่น้อยที่สุด จุดอ่อนของอัลกอริทึมคือมันค่อนข้างแพง ฉันไม่มีเวลาเพิ่มเกิน 1,000 คะแนนและภาพจุด 1,000 คู่ยังคงได้รับการปรับปรุง

(คลิกขวาและดูภาพเพื่อรับเวอร์ชั่นที่ใหญ่กว่า)

รูปย่อสำหรับ 100, 300 และ 1,000 คะแนน

ลิงก์ไปยังรุ่นที่ใหญ่กว่าคือhttp://imgur.com/a/2IXDT#9 (100 คะแนน), http://imgur.com/a/bBQ7q (300 คะแนน) และhttp://imgur.com/a/rr8wJ (1,000 คะแนน)

#!/usr/bin/env python

import glob
import os

import scipy.misc
import scipy.spatial
import scipy.signal
import numpy as N
import numpy.random as NR
import emcee

def compute_image(pars, rimg, gimg, bimg):
    npts = len(pars) // 2
    x = pars[:npts]
    y = pars[npts:npts*2]
    yw, xw = rimg.shape

    # exit if points are too far away from image, to stop MCMC
    # wandering off
    if(N.any(x > 1.2*xw) or N.any(x < -0.2*xw) or
       N.any(y > 1.2*yw) or N.any(y < -0.2*yw)):
        return None

    # compute tesselation
    xy = N.column_stack( (x, y) )
    tree = scipy.spatial.cKDTree(xy)

    ypts, xpts = N.indices((yw, xw))
    queryxy = N.column_stack((N.ravel(xpts), N.ravel(ypts)))

    dist, idx = tree.query(queryxy)

    idx = idx.reshape(yw, xw)
    ridx = N.ravel(idx)

    # tesselate image
    div = 1./N.clip(N.bincount(ridx), 1, 1e99)
    rav = N.bincount(ridx, weights=N.ravel(rimg)) * div
    gav = N.bincount(ridx, weights=N.ravel(gimg)) * div
    bav = N.bincount(ridx, weights=N.ravel(bimg)) * div

    rout = rav[idx]
    gout = gav[idx]
    bout = bav[idx]
    return rout, gout, bout

def compute_fit(pars, img_r, img_g, img_b):
    """Return fit statistic for parameters."""
    # get model
    retn = compute_image(pars, img_r, img_g, img_b)
    if retn is None:
        return -1e99
    model_r, model_g, model_b = retn

    # maximum squared deviation from one of the chanels
    fit = max( ((img_r-model_r)**2).sum(),
               ((img_g-model_g)**2).sum(),
               ((img_b-model_b)**2).sum() )

    # return fake log probability
    return -fit

def convgauss(img, sigma):
    """Convolve image with a Gaussian."""
    size = 3*sigma
    kern = N.fromfunction(
        lambda y, x: N.exp( -((x-size/2)**2+(y-size/2)**2)/2./sigma ),
        (size, size))
    kern /= kern.sum()
    out = scipy.signal.convolve2d(img.astype(N.float64), kern, mode='same')
    return out

def process_image(infilename, outroot, npts):
    img = scipy.misc.imread(infilename)
    img_r = img[:,:,0]
    img_g = img[:,:,1]
    img_b = img[:,:,2]

    # scale down size
    maxdim = max(img_r.shape)
    scale = int(maxdim / 150)
    img_r = img_r[::scale, ::scale]
    img_g = img_g[::scale, ::scale]
    img_b = img_b[::scale, ::scale]

    # make unsharp-masked image of input
    img_tot = N.max((img_r, img_g, img_b), axis=0)
    img1 = convgauss(img_tot, 2)
    img2 = convgauss(img_tot, 32)
    diff = N.abs(img1 - img2)
    diff = diff/diff.max()
    diffi = (diff*255).astype(N.int)
    scipy.misc.imsave(outroot + '_unsharp.png', diffi)

    # create random points with a probability distribution given by
    # the unsharp-masked image
    yw, xw = img_r.shape
    xpars = []
    ypars = []
    while len(xpars) < npts:
        ypar = NR.randint(int(yw*0.02),int(yw*0.98))
        xpar = NR.randint(int(xw*0.02),int(xw*0.98))
        if diff[ypar, xpar] > NR.rand():
            xpars.append(xpar)
            ypars.append(ypar)

    # initial parameters to model
    allpar = N.concatenate( (xpars, ypars) )

    # set up MCMC sampler with parameters close to each other
    nwalkers = npts*5  # needs to be at least 2*number of parameters+2
    pos0 = []
    for i in xrange(nwalkers):
        pos0.append(NR.normal(0,1,allpar.shape)+allpar)

    sampler = emcee.EnsembleSampler(
        nwalkers, len(allpar), compute_fit,
        args=[img_r, img_g, img_b],
        threads=4)

    # sample until we don't find a better fit
    lastmax = -N.inf
    ct = 0
    ct_nobetter = 0
    for result in sampler.sample(pos0, iterations=10000, storechain=False):
        print ct
        pos, lnprob = result[:2]
        maxidx = N.argmax(lnprob)

        if lnprob[maxidx] > lastmax:
            # write image
            lastmax = lnprob[maxidx]
            mimg = compute_image(pos[maxidx], img_r, img_g, img_b)
            out = N.dstack(mimg).astype(N.int32)
            out = N.clip(out, 0, 255)
            scipy.misc.imsave(outroot + '_binned.png', out)

            # save parameters
            N.savetxt(outroot + '_param.dat', scale*pos[maxidx])

            ct_nobetter = 0
            print(lastmax)

        ct += 1
        ct_nobetter += 1
        if ct_nobetter == 60:
            break

def main():
    for npts in 100, 300, 1000:
        for infile in sorted(glob.glob(os.path.join('images', '*'))):
            print infile
            outroot = '%s/%s_%i' % (
                'outdir',
                os.path.splitext(os.path.basename(infile))[0], npts)

            # race condition!
            lock = outroot + '.lock'
            if os.path.exists(lock):
                continue
            with open(lock, 'w') as f:
                pass

            process_image(infile, outroot, npts)

if __name__ == '__main__':
    main()

ภาพที่ไม่มีรูปทรงหน้ากากมีลักษณะดังนี้ จุดสุ่มถูกเลือกจากภาพหากตัวเลขสุ่มน้อยกว่าค่าของภาพ (กำหนดเป็น 1):

ภาพดาวเสาร์ที่ไม่ได้สวมหน้ากาก

ฉันจะโพสต์ภาพที่ใหญ่ขึ้นและคะแนน Voronoi ถ้าฉันมีเวลามากขึ้น

แก้ไข: หากคุณเพิ่มจำนวนวอล์กเกอร์เป็น 100 * npts ให้เปลี่ยนฟังก์ชั่นค่าใช้จ่ายเป็นสี่เหลี่ยมจัตุรัสส่วนเบี่ยงเบนในทุกช่องสัญญาณและรอเป็นเวลานาน (เพิ่มจำนวนการวนซ้ำเพื่อแยกวงเป็น 200) เป็นไปได้ที่จะสร้างภาพที่ดีด้วยคะแนนเพียง 100 คะแนน:

ภาพ 11, 100 คะแนน ภาพ 2, 100 คะแนน ภาพที่ 4, 100 คะแนน Image 10, 100 คะแนน


3

การใช้พลังงานภาพเป็นแผนที่จุดน้ำหนัก

ในแนวทางของฉันต่อความท้าทายนี้ฉันต้องการวิธีทำแผนที่ "ความเกี่ยวข้อง" ของพื้นที่รูปภาพหนึ่ง ๆ กับความน่าจะเป็นที่จะเลือกจุดใดจุดหนึ่งเป็น Voronoi centroid อย่างไรก็ตามฉันยังต้องการที่จะรักษาความรู้สึกทางศิลปะของโมเสก Voronoi โดยการเลือกจุดภาพแบบสุ่ม นอกจากนี้ฉันต้องการทำงานกับภาพขนาดใหญ่ดังนั้นฉันจะไม่สูญเสียสิ่งใดในกระบวนการสุ่มตัวอย่าง อัลกอริทึมของฉันเป็นแบบนี้:

  1. สำหรับแต่ละภาพสร้างแผนที่ความคมชัด แผนที่ความคมชัดถูกกำหนดโดยพลังงานภาพปกติ (หรือกำลังสองของสัญญาณความถี่สูงของภาพ) ตัวอย่างมีลักษณะดังนี้:

แผนที่ความคมชัด

  1. สร้างจำนวนจุดจากภาพโดยรับ 70 เปอร์เซ็นต์จากจุดในแผนที่ความคมชัดและ 30 เปอร์เซ็นต์จากจุดอื่น ๆ ทั้งหมด ซึ่งหมายความว่าจุดตัวอย่างจะมีความหนาแน่นมากขึ้นจากส่วนที่มีรายละเอียดสูงของภาพ
  2. สี!

ผล

N = 100, 500, 1,000, 3000

ภาพ 1, N = 100 ภาพ 1, N = 500 ภาพ 1, N = 1,000 ภาพ 1, N = 3000

รูปภาพ 2, N = 100 รูปภาพ 2, N = 500 รูปภาพ 2, N = 1,000 รูปภาพ 2, N = 3000

รูปภาพ 3, N = 100 รูปภาพ 3, N = 500 รูปภาพ 3, N = 1,000 ภาพ 3, N = 3000

รูปภาพ 4, N = 100 รูปภาพ 4, N = 500 รูปภาพ 4, N = 1,000 รูปภาพ 4, N = 3000

ภาพ 5, N = 100 ภาพ 5, N = 500 ภาพ 5, N = 1,000 ภาพ 5, N = 3000

ภาพ 6, N = 100 ภาพ 6, N = 500 ภาพ 6, N = 1,000 ภาพ 6, N = 3000

รูปภาพ 7, N = 100 ภาพที่ 7, N = 500 รูปที่ 7, N = 1,000 ภาพที่ 7, N = 3000

ภาพ 8, N = 100 ภาพ 8, N = 500 ภาพ 8, N = 1,000 ภาพ 8, N = 3000

ภาพ 9, N = 100 ภาพ 9, N = 500 รูปที่ 9, N = 1,000 ภาพที่ 9, N = 3000

ภาพ 10, N = 100 ภาพ 10, N = 500 ภาพ 10, N = 1,000 ภาพ 10, N = 3000

ภาพ 11, N = 100 ภาพ 11, N = 500 ภาพ 11, N = 1,000 ภาพ 11, N = 3000

ภาพ 12, N = 100 รูปภาพ 12, N = 500 ภาพ 12, N = 1,000 รูปที่ 12, N = 3000

รูปภาพ 13, N = 100 รูปภาพ 13, N = 500 รูปภาพ 13, N = 1,000 รูปภาพ 13, N = 3000

รูปภาพ 14, N = 100 รูปภาพ 14, N = 500 ภาพที่ 14, N = 1,000 ภาพที่ 14, N = 3000


14
คุณจะรังเกียจไหมก) รวมถึงซอร์สโค้ดที่ใช้ในการสร้างสิ่งนี้และข) ลิงก์ภาพขนาดย่อแต่ละภาพกับภาพขนาดเต็มหรือไม่
Martin Ender
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.