Voronoi Haritası Olarak Resim Çekme


170

Calvin'in Hobileri'ne meydan okuma fikrimi doğru yöne sokmak için teşekkür eder.

Düzlemde, siteler diyeceğimiz bir nokta kümesi düşünün ve her bir site ile bir renk ilişkilendirin. Artık her bir noktayı en yakın sitenin rengiyle boyayarak tüm uçağı boyayabilirsiniz. Buna Voronoi haritası (veya Voronoi diyagramı ) denir . Prensip olarak, Voronoi haritaları herhangi bir mesafe ölçümü için tanımlanabilir, ancak basitçe normal Öklid mesafesini kullanacağız r = √(x² + y²). ( Not: Bu zorlukla mücadele etmek için bunlardan birinin nasıl hesaplanacağını ve oluşturulacağını bilmek zorunda değilsiniz.)

İşte 100 siteye bir örnek:

görüntü tanımını buraya girin

Herhangi bir hücreye bakarsanız, o hücre içindeki tüm noktalar ilgili bölgeye diğer bölgelere göre daha yakındır.

Göreviniz, bu tür bir Voronoi haritasıyla verilen bir görüntüye yaklaşmak. Görüntüye herhangi bir uygun raster grafik biçiminde ve tamsayı N de verilir . Daha sonra en fazla N site ve her bir site için bir renk üretmelisiniz , öyle ki bu siteleri temel alan Voronoi haritası giriş görüntüsünü mümkün olduğu kadar benzemektedir.

Çıktınızdan bir Voronoi haritası oluşturmak için bu zorluğun altındaki Stack Snippet'i kullanabilir ya da isterseniz kendiniz oluşturabilirsiniz.

Sen olabilir (eğer gerekiyorsa) sitelerinin bir kümesinden bir Voronoi harita hesaplamak için yerleşik veya üçüncü taraf işlevlerini kullanın.

Bu bir popülerlik yarışmasıdır, bu nedenle en net oyları içeren cevap kazanır. Seçmenlerin cevaplarını yargılamaya teşvik etmesi

  • Orijinal görüntülerin ve renklerinin ne kadar iyi yaklaştığı.
  • Algoritmanın farklı görüntü türleri üzerinde ne kadar iyi çalıştığı.
  • algoritma küçük N için ne kadar iyi çalışıyor ?
  • algoritmanın uyarlamalı olarak kümelemesi olup olmadığı, görüntünün daha fazla ayrıntı gerektiren bölgelerine işaret eder.

Test görüntüleri

Algoritmanızı test etmek için birkaç görüntü (bazı olağan şüphelilerimizden bazıları yeni bazıları). Büyük versiyonlar için resimlere tıklayınız.

Büyük dalga Kirpi plaj Cornell Satürn Kahverengi ayı Yoshi mandril Yengeç Bulutsusu Geobits'in Çocuğu Şelale Çığlık

İlk sıradaki plaj Olivia Bell tarafından çizildi ve iznine dahil edildi.

Ekstra bir meydan okuma istiyorsanız, Yoshi'yi beyaz bir arka planla deneyin ve karnı doğru olsun.

Tüm bu test resimlerini , hepsini zip dosyası olarak indirebileceğiniz bu imgur galerisinde bulabilirsiniz. Albüm ayrıca başka bir test olarak rastgele bir Voronoi diyagramı içeriyor. Başvuru için, burada onu üreten veriler .

Lütfen çeşitli farklı görüntüler ve N , örneğin 100, 300, 1000, 3000 (ve bunlara karşılık gelen hücre spesifikasyonlarının bazılarına pastebin) için örnek diyagramlar ekleyin . Uygun gördüğünüz gibi hücreler arasında siyah kenarları kullanabilir veya atlayabilirsiniz (bu, bazı görüntülerde diğerlerinden daha iyi görünebilir). Ancak siteleri eklemeyin (ayrı bir örnek hariç, belki de site yerleşiminizin nasıl çalıştığını açıklamak istiyorsanız).

Çok sayıda sonuç göstermek istiyorsanız , cevapların boyutunu makul tutmak için imgur.com adresinde bir galeri oluşturabilirsiniz . Alternatif olarak, gönderiminize küçük resimler koyun ve referans yanıtımda yaptığım gibi daha büyük resimlere bağlantı vermelerini sağlayın . Küçük resimleri simgur.com linkindeki dosya adına ekleyerek alabilirsiniz (örn. I3XrT.png-> I3XrTs.png). Ayrıca, hoş bir şey bulursanız diğer test görüntülerini kullanmaktan çekinmeyin.

Oluşturucu

Sonuçlarınızı oluşturmak için çıktınızı aşağıdaki Yığın Parçacığına yapıştırın. Kesin liste formatı, her hücre sırayla 5 kayan nokta sayısıyla belirtildiği sürece , hücrenin sahasının koordinatları x y r g bolduğu xve aralıktaki kırmızı, yeşil ve mavi renk kanalları olduğu sürece önemsizdir .yr g b0 ≤ r, g, b ≤ 1

Snippet, hücre kenarlarının çizgi genişliğini ve hücre sitelerinin gösterilip gösterilmeyeceğini belirten seçenekler sunar (ikincisi çoğunlukla hata ayıklama amaçları için). Ancak çıktının yalnızca hücre özellikleri değiştiğinde yeniden oluşturulduğunu unutmayın; bu nedenle diğer seçeneklerden bazılarını değiştirirseniz, hücrelere veya başka şeylere bir boşluk ekleyin.

Bu gerçekten güzel JS Voronoi kütüphanesini yazdığı için Raymond Hill'e büyük krediler .

İlgili Zorluklar


5
@frogeyedpeas Aldığınız oylara bakarak. ;) Bu bir popülerlik yarışması. Yapmanın en iyi yolu mutlaka yoktur. Fikir, mümkün olduğu kadar iyi yapmaya çalışmanızdır ve oylar insanların iyi bir iş yaptığınız konusunda hemfikir olup olmadıklarını yansıtacaktır. Kuşkusuz, bunlarda belirli bir öznellik var. Ben bağlantılı veya ilgili zorlukları göz at bu bir . Genelde çok çeşitli yaklaşımların olduğunu göreceksiniz, ancak oylama sistemi daha iyi çözümlerin zirveye çıkmasına ve kazananlara karar vermesine yardımcı oluyor.
Martin Ender

3
Olivia, bugüne kadar sunulan plajının yaklaşımlarını onayladı.
Alex A.

3
@AlexA. Devon, şu ana kadar sunulan yüzünün bazı yaklaşımlarını onayladı . N = 100 versiyondan herhangi birinin büyük bir hayranı değil;)
Geobits

1
@Geobits: Büyüdüğünde anlayacaktır.
Alex A.

1
İşte centroidal voronoi tabanlı stippling tekniği hakkında bir sayfa . İyi bir ilham kaynağı (ilgili yüksek lisans tezi algoritmadaki olası iyileştirmeler hakkında güzel bir tartışma).
İş

Yanıtlar:


112

Piton + scipy + scikit görüntü , ağırlıklı Poisson disk örnekleme

Benim çözümüm oldukça karmaşık. Gürültüyü gidermek ve her noktanın ne kadar 'ilginç' olduğunu gösteren bir harita almak için görüntü üzerinde bazı işlemler yapıyorum (yerel entropi ve kenar algılama kombinasyonunu kullanarak):

Daha sonra Poisson disk örnekleme kullanarak bir bükülme kullanarak örnekleme noktaları seçiyorum : dairenin uzaklığı daha önce belirlediğimiz ağırlıkla belirlenir.

Sonra örnekleme noktalarına sahip olduktan sonra görüntüyü voronoi segmentlerine bölüştürürüm ve her segment içindeki her bir renk için L * a * b * ortalamasını atarım.

Çok fazla sezgisel deneyime sahibim ve ayrıca örnek noktaların sayısının yakın olduğundan emin olmak için biraz matematik yapmalıyım N. NTam olarak hafifçe atarak ve sonra bir buluşsalla bazı noktaları bırakarak alıyorum .

Çalışma zamanı açısından, bu filtre ucuz değildir , ancak aşağıdaki görüntülerin elde edilmesi 5 saniyeden uzun sürmedi.

Daha fazla uzatmadan:

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!")

Görüntüler

Sırasıyla N100, 300, 1000 ve 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
Bunu severim; biraz füme cam gibi görünüyor.
BobTheAwesome

3
Bununla biraz uğraştım ve denoise_bilatteral bir denoise_tv_bregman ile değiştirirseniz, özellikle düşük üçgen görüntüler için daha iyi sonuçlar elde edersiniz. Yardımcı olan, seslendirme işleminde daha fazla düzeltme eki oluşturur.
LKlevin

@LKlevin Hangi ağırlığı kullandınız?
orlp

Ağırlık olarak 1.0 kullandım.
LKlevin

65

C ++

Yaklaşımım oldukça yavaş, ama özellikle kenarları korumak konusunda verdiği sonuçlardan çok memnunum. Örneğin, her biri yalnızca 1000 siteyle birlikte Yoshi ve Cornell Box :

İğnelemeyi sağlayan iki ana parça var. İlki, evaluate()fonksiyonda somutlaşan bir dizi aday site yeri alır, bunlara en uygun renkleri ayarlar ve PSNR hedef görüntüye karşı işlenen Voronoi mozaiğine ait. Her sitenin renkleri, sitenin etrafındaki hücre tarafından kapsanan hedef görüntü piksellerinin ortalaması alınarak belirlenir. Kullandığım Welford algoritmasını her bir hücre için en iyi renk ve varyans, MSE ve PSNR arasındaki ilişkiyi istismar ederek resmin üzerine sadece tek bir geçiş kullanılarak elde edilen PSNR hem hesaplamak yardımcı olur. Bu, sorunu, rengi dikkate almadan, en iyi site konumlarını bulma konusunda birine indirger.

Sonra ikinci bölüm, somutlaştırılmış main() bu seti bulmaya çalışır. Rasgele bir dizi nokta seçerek başlar. Daha sonra, her adımda bir puan kaldırır (gidiş-dönüş rotasından) ve yerini almak için rastgele bir aday noktaları test eder. Demet en yüksek PSNR veren bir kişi kabul edilir ve saklanır. Etkili, bu sitenin yeni bir yere atlamasına neden olur ve genellikle görüntüyü bit bit iyileştirir. Algoritmanın kasıtlı olarak orijinal konumu bir aday olarak tutmadığını unutmayın . Bazen bu, atlamanın genel görüntü kalitesini düşürdüğü anlamına gelir. Bunun olmasına izin vermek, yerel maksimumda takılıp kalmamaya yardımcı olur. Ayrıca durma kriterleri verir; Program, bugüne kadar bulunan en iyi siteler üzerinde iyileştirme yapmadan belli sayıda adım attıktan sonra sona erer.

Bu uygulamanın oldukça basit olduğunu ve özellikle site sayısı arttıkça saatlerce CPU çekirdeğinin zaman alabileceğini unutmayın. Her aday için eksiksiz Voronoi haritasını yeniden hesaplar ve kaba kuvvet her piksel için tüm sitelere olan mesafeyi test eder. Her işlem bir seferde bir noktayı kaldırmayı ve başka bir eklemeyi içerdiğinden, her adımdaki görüntüdeki gerçek değişiklikler oldukça yerel olacaktır. Bir Voronoi diyagramını etkin bir şekilde arttırmak için algoritmalar var ve bu algoritmaya muazzam bir hız kazandırdıklarına inanıyorum. Bununla birlikte, bu yarışma için, işleri basit ve kaba bir şekilde tutmayı seçtim.

kod

#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;
 }

Koşu

Program kendi kendine yeten ve standart kütüphanenin ötesinde herhangi bir dış bağımlılığa sahip değil, ancak görüntülerin ikili PPM formatında olmasını gerektiriyor . ImageMagick'i görüntüleri PPM'ye dönüştürmek için kullanıyorum , ancak GIMP ve diğer birkaç program da bunu yapabiliyor.

Derlemek için programı farklı kaydedin voronoi.cppve sonra çalıştırın:

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

Bunu denememiş olmama rağmen, muhtemelen Visual Studio'nun son sürümleri ile Windows üzerinde çalışacağını umuyorum. C ++ 11 veya daha iyisi ile derlediğinizden ve OpenMP'nin etkin olduğundan emin olmak isteyeceksiniz. OpenMP kesinlikle gerekli değildir, ancak yürütme sürelerini daha tolere edilebilir hale getirmede çok yardımcı olur.

Çalıştırmak için, şöyle bir şey yapın:

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

Daha sonraki dosya, olduğu gibi site verileriyle güncellenir. İlk satır, görüntünün genişliğine ve yüksekliğine sahip olacak ve ardından problem açıklamasında Javascript oluşturucuya kopyalamak ve yapıştırmak için uygun x, y, r, g, b değerlerinin satırlarını izleyecektir.

Programın üstündeki üç sabit, kaliteye karşı hız için ayarlamanızı sağlar. decimationRenk ve PSNR için site kümesini değerlendirirken faktör hedef resim kabalaştırır. Ne kadar yüksek olursa program o kadar hızlı çalışır. 1 olarak ayarlamak tam çözünürlüklü görüntüyü kullanır. candidatesSabit kontroller kaç adaylar her adım test etmek. Yüksek, daha iyi atlamak için iyi bir yer bulma şansını verir, ancak programı yavaşlatır. Son olarak, terminationprogram çıkmadan önce çıktısını iyileştirmeden kaç adım atılabileceğidir. Artırılması daha iyi sonuçlar verebilir, ancak marjinal olarak daha uzun sürebilir.

Görüntüler

N = 100, 300, 1000 ve 3000:


1
Bu IMO'yu kazanmalıydı - benimkinden çok daha iyi.
orlp

1
@orlp - Teşekkürler! Adil olmak gerekirse, sizinkini çok daha önce yayınladınız ve çok daha hızlı çalışıyor. Hız sayar!
Boojum

1
Benimki gerçekten bir voronoi haritası cevabı değil :) Gerçekten de iyi bir örnekleme algoritması, fakat örnek noktaları voronoi sitelerine çevirmek kesinlikle uygun değil.
orlp

55

IDL, uyarlanabilir arıtma

Bu yöntem, astronomik simülasyonlardan Adaptive Mesh Refinement ve aynı zamanda Subvision bölmesinden ilham almıştır . Bu, IDL'nin kendisiyle gurur duyduğu, kullanabileceğim çok sayıda yerleşik işlevle anlatabileceğiniz bir görev türü. : D

Siyah arkaplan yoshi test görüntüsü için bazı ara ürünleri çıktı n = 1000.

İlk önce, görüntüye (kullanarak ct_luminance) parlaklık gri tonu uygularız ve iyi kenar tespiti için bir Prewitt filtresi uygular ( prewittbkz. Wikipedia ):

ABC ABC

Ardından gerçek homurdanma işi geliyor: görüntüyü 4'e bölüyoruz ve filtrelenen görüntüdeki her dört kadrandaki varyansı ölçüyoruz. Varyansımız alt bölümün büyüklüğüne göre ağırlıklandırılır (bu ilk adımda eşittir), böylece yüksek değişkenliği olan "sinirli" bölgeler daha küçük, daha küçük ve daha küçük bölümlere ayrılmaz. Daha sonra, alt bölümleri daha ayrıntılı bir şekilde hedeflemek için ağırlıklı varyansı kullanırız ve hedef detay sitelerimizi (her alt bölüm tam olarak bir site içerir) isabet edinceye kadar, her ayrıntı yönünden zengin bölümü tekrar tekrar 4'e böleriz. Her yinelemede 3 site eklediğimizden, sonunda n - 2 <= N <= nsitelerle karşılaşıyoruz.

Gömemediğim bu görüntünün alt bölümleme işleminden bir .webm yaptım, ama işte burada ; Her bir alt bölümdeki renk, ağırlıklı varyansa göre belirlenir. (Beyaz arkaplan yoshi için aynı tür videoyu karşılaştırdım, karşılaştırma için, renk tablosu ters çevrildi, böylece siyah yerine beyaz olacak; işte burada .) Alt bölümün son ürünü şöyle görünür:

ABC

Alt bölümler listemize girdikten sonra, her alt bölümden geçiyoruz. Nihai site konumu, Prewitt görüntüsünün minimumunun, yani en az "sinirli" pikselin pozisyonudur ve bölümün rengi, o pikselin rengidir; işte orijinal resim, siteleri işaretli olarak:

ABC

Sonra, triangulatesitelerin Delaunay üçgenlemesini hesaplamak için yerleşikleri ve voronoiher çokgenleri kendi renklerinde bir görüntü arabelleğine çizmeden önce her bir Voronoi poligonunun köşelerini tanımlamak için yerleşikleri kullanırız. Son olarak, görüntü arabelleğinin anlık görüntüsünü kaydederiz.

ABC

Kod:

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

Bunu üzerinden ara voro_map, n, image, output_filename. wrapperHer test görüntüsünden geçen ve 100, 500 ve 1000 siteyle çalışan bir prosedür de dahil ettim .

Çıktı burada toplandı ve işte bazı küçük resimler:

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
Bu çözümün daha karmaşık alanlarda daha fazla puan vermesini gerçekten seviyorum, bu nedenle niyetin olduğunu düşünüyorum ve bu noktada diğerlerinden ayıracağım.
alexander-brett

evet, ayrıntılı gruplanmış noktalar fikri beni adapte arıtmaya yönlendiren şeydir
sirpercival

3
Çok temiz bir açıklama ve görüntüler etkileyici! Bir sorum var - Yoshi birkaç tuhaf şekle sahip olduğumuz beyaz bir arka plan üzerindeyken çok farklı görüntüler elde ettiğiniz anlaşılıyor. Buna ne sebep olabilir?
BrainSteel

2
@BrianSteel Ana hatları, yüksek değişkenlikli alanlar olarak seçildiğini ve gereksiz yere odaklandığını hayal ediyorum ve daha sonra diğer gerçekten yüksek detay alanlarına bunun için daha az puan veriliyor .
doppelgreener 17:15

@BrainSteel bence doppel haklıdır - siyah sınır ile algoritmada çok fazla ayrıntı isteyen siyah arka plan arasında güçlü bir kenar var. Bu i (veya, daha da önemlisi, bir şey olup olmadığını emin değilim olmalıdır ) düzeltmek ...
sirpercival

47

Python 3 + PIL + SciPy, Bulanık k-araçları

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)

Algoritması

Temel fikir, k-araçlarının doğal olarak görüntüyü Voronoi hücrelerine bölmesidir, çünkü noktalar en yakın centroid'e bağlıdır. Ancak, renkleri bir şekilde sınırlama olarak eklememiz gerekir.

Öncelikle daha iyi renk manipülasyonu için her pikseli Lab renk uzayına dönüştürüyoruz .

Sonra bir çeşit "zavallı adamın kenar tespiti" yapıyoruz. Her piksel için ortogonal ve diyagonal komşularına bakarız ve renkteki ortalama kare farkını hesaplar. Ardından tüm pikselleri bu farklara göre sıralıyoruz, listenin önündeki komşularına en çok benzeyen pikselleri ve arkadaki komşularına en çok benzeyen pikselleri (yani, bir uç nokta olması daha olasıdır). İşte piksel için daha parlak olan, komşularından daha farklı olan gezegene bir örnek:

görüntü tanımını buraya girin

(Yukarıdaki görüntülenen çıktıda net bir ızgara benzeri desen var.

Daha sonra, bu piksel sırasını kümelenecek çok sayıda noktayı örneklemek için kullanırız. Daha ümit verici bir dağıtım kullanıyoruz, daha keskin ve "ilginç" noktalara öncelik veriyoruz.

görüntü tanımını buraya girin

Kümeleme için, ilk önce Naynı üstel dağılım kullanılarak rasgele seçilen centroidleri seçtik. Bir ilk yineleme gerçekleştirilir ve ortaya çıkan kümelerin her biri için ortalama bir renk ve bir renk farkı eşiği atarız. Sonra birkaç yineleme için biz:

  • Santrallerin Delaunay üçgenlemesini oluşturun, böylece komşuları kolayca centroidlere sorgulayabilelim.
  • Komşularının ve komşularının komşularının çoğuna (> 4/5) yakın renkli olan sentroidleri kaldırmak için üçgenlemeyi kullanın. İlgili herhangi bir numune noktası da kaldırılır ve yeni ikame centroidler ve numune noktaları eklenir. Bu adım, algoritmayı ayrıntıya ihtiyaç duyulan yere daha fazla küme yerleştirmeye zorlar.
  • Yeni centroidler için bir kd ağacı oluşturun, böylece en yakın centroidleri herhangi bir örnek noktaya kolayca sorgulayabiliriz.
  • Her örnek noktasını en yakın 6 merkezden birine (6 ampirik olarak seçilen) atamak için ağacı kullanın. Bir centroid, yalnızca noktanın rengi centroid'in renk farkı eşiğindeyse bir örnek noktası kabul eder. Her bir örnek noktayı ilk kabul eden merkeze atamaya çalışıyoruz, ancak bu mümkün değilse o zaman en yakın merkeze atayalım. Kümelerin üst üste gelmesi mümkün olduğundan, algoritmanın "bulanıklığı" bu adımdan gelir.
  • Centroidleri yeniden hesapla.

görüntü tanımını buraya girin

(Resmin tamamı için tıklayınız)

Son olarak, bu sefer homojen bir dağılım kullanarak çok sayıda nokta örnekliyoruz. Başka bir kd ağacı kullanarak, her noktayı en yakın centroidine atarız ve kümeler oluşturur. Daha sonra her bir kümenin ortanca rengini bir tepe tırmanma algoritması kullanarak yaklaşık hücre renklerimizi veririz (@PhiNotPi ve @ MartinBüttner sayesinde bu adım için fikir).

görüntü tanımını buraya girin

notlar

Snippet ( OUTFILE) için bir metin dosyası çıkarmaya ek olarak , programa DEBUGayarlanmışsa Trueyukarıdaki görüntülerin çıktısını alır ve üzerine yazar. Algoritmanın her görüntü için birkaç dakika sürmesi nedeniyle, süreye çok fazla şey eklemeyen ilerlemeyi kontrol etmenin iyi bir yolu.

Örnek çıktılar

N = 32:

görüntü tanımını buraya girin

N = 100:

görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin

N = 1000:

görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin

N = 3000:

görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin


1
Beyazındaki Yoshis'in ne kadar iyi göründüğünü gerçekten çok beğendim.
Maksimum

26

Mathematica, Rastgele Hücreler

Bu temel çözümdür, sizden sorduğum asgari fikir hakkında bir fikir edinebilirsiniz. Dosya adı (yerel olarak veya bir URL olarak) içinde fileve içinde N olarak nverildiğinde, aşağıdaki kod sadece N rasgele pikselleri seçer ve bu piksellerde bulunan renkleri kullanır. Bu gerçekten saf ve inanılmaz derecede iyi çalışmıyor, ama sonuçta sizden bunu yenmenizi istiyorum. :)

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

İşte N = 100 için tüm test görüntüleri (tüm görseller daha büyük sürümlere bağlantı):

görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin

Gördüğünüz gibi, bunlar aslında işe yaramaz. Sanatsal bir değeri olsalar da, dışavurumcu bir şekilde, orijinal imgeler zar zor tanınabiliyor.

İçin N = 500 , durum biraz düzeldi, ama çok garip eserler görüntüleri yıkanmış bakmak ve hücrelerin çok detay olmadan bölgelere israf edilmektedir, hala var:

görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin görüntü tanımını buraya girin

Senin sıran!


Ben iyi bir kodlayıcı değilim ama tanrım bu görüntüler güzel görünüyor. Harika fikir!
Faraz Masroor

Bir nedeni var mı Dimensions@ImageDatayok ImageDimensionsmu? Yavaş yavaş ImageDatatamamen kullanarak önleyebilirsiniz PixelValue.
2012rampampion

@ 2012rcampion Sebep yok, sadece iki fonksiyonun da olduğunu bilmiyordum. Bunu daha sonra düzeltebilir ve ayrıca örnek görüntüleri önerilen N değerlerine değiştirebilirim.
Martin Ender

23

Mathematica

Hepimiz Martin'in Mathematica'yı sevdiğini biliyoruz, bu yüzden bunu bir Mathematica ile deneyeyim.

Algoritmam, ilk voronoi diyagramı oluşturmak için görüntü kenarlarından rastgele noktaları kullanır. Diyagram daha sonra basit bir ortalama filtre ile ağın yinelemeli bir ayarlaması ile hazırlanır. Bu, yüksek kontrast bölgelere yakın yüksek hücre yoğunluğuna ve çılgın açıları olmayan görsel olarak hoş hücrelere sahip görüntüler sağlar.

Aşağıdaki resimler, işlemdeki bir örneği göstermektedir. Eğlence biraz Mathematicas kötü Antialiasing tarafından şımarık, ama biz buna değer olmalı, vektör grafikleri alıyoruz.

Bu algoritma, rastgele örnekleme olmadan bulunabilir VoronoiMeshbelgeler burada .

görüntü tanımını buraya girin görüntü tanımını buraya girin

Test Görüntüleri (100.300.1000.3000)

kod

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]}]]
   ];

İlk yazı için iyi iş! :) Voronoi test görüntüsünü 32 hücreyle denemek isteyebilirsiniz (görüntüdeki hücrelerin sayısı budur).
Martin Ender

Teşekkürler! Sanırım algoritmam bu örnekte çok iyi performans gösterecek. Tohumlar hücre kenarlarında başlatılacak ve özyineleme bunu daha iyi hale getirmeyecek;)
pençe

Orijinal görüntüye yakınlaşma hızına rağmen, algoritmanızın çok sanatsal bir sonuç verdiğini biliyorum! (Georges Seurat eserlerinin iyileştirilmiş bir versiyonu gibi). İyi iş!
neizod

Ayrıca son çizgilerinizi değiştirerek camsı görünümlü enterpolasyonlu çokgen renkler elde edebilirsinizGraphics@Table[ Append[mp, VertexColors -> RGBColor /@ ImageValue[img, First[mp]]], {mp, MeshPrimitives[voronoiRelaxed, 2]}]
Histogramlar

13

Python + SciPy + sergilemek

Kullandığım algoritma şudur:

  1. Görüntüleri küçük bir boyuta getirin (~ 150 piksel)
  2. Maksimum kanal değerlerinin keskin olmayan maskeli bir görüntüsünü alın (bu, beyaz bölgeleri çok fazla almamaya yardımcı olur).
  3. Mutlak değeri al.
  4. Bu görüntüye orantılı olasılıkla rastgele noktalar seçin. Bu, süreksizliklerin her iki tarafını da seçer.
  5. Bir maliyet fonksiyonunu düşürmek için seçilen noktaları iyileştirin. İşlev, kanallardaki kare sapmaların toplamının maksimumudur (tekrar düz renklere değil, yalnızca düz beyaza önyargıya yardımcı olur). Ben Markov Zinciri Monte Carlo'yu, sergileme modülü olarak (şiddetle tavsiye edilen) emcee modülü ile yanlış kullandım. N zinciri yinelemesinden sonra yeni bir gelişme bulunmadığında prosedür kurtarılır.

Algoritma çok iyi çalışıyor gibi görünüyor. Ne yazık ki, sadece ufacık görüntüler üzerinde makul bir şekilde çalışabilir. Voronoi puanlarını almak ve bunları daha büyük görüntülere uygulamak için zamanım olmadı. Bu noktada rafine edilebilirler. Minimumu daha iyi alabilmek için MCMC'yi daha uzun süre çalıştırabilirdim. Algoritmanın zayıf noktası, oldukça pahalı olmasıdır. 1000 puanın ötesine geçecek zamanım olmadı ve 1000 puanlık görüntülerin bir kısmı hala rafine ediliyor.

(daha büyük bir sürüm elde etmek için sağ tıklayın ve görüntüyü görüntüleyin)

100, 300 ve 1000 puan için küçük resimler

Daha büyük sürümlere bağlantılar http://imgur.com/a/2IXDT#9 (100 puan), http://imgur.com/a/bBQ7q (300 puan) ve http://imgur.com/a/rr8wJ'dir. (1000 puan)

#!/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()

Keskin maskelenmiş görüntüler aşağıdakine benzer. Rastgele bir sayı görüntünün değerinden azsa (1'e göre normal), rastgele noktalar görüntüden seçilir:

Keskin maskeli Satürn resmi

Daha fazla zaman alırsam daha büyük görüntüler ve Voronoi puanları gönderirim.

Düzenleme: Yürüteç sayısını 100 * npts'ye yükseltirseniz, maliyet işlevini tüm kanallardaki sapmaların karelerinden biri olacak şekilde değiştirin ve uzun bir süre bekleyin (döngüden çıkmak için yineleme sayısını artırmak 200), sadece 100 puanla iyi görüntüler elde etmek mümkün:

Resim 11, 100 puan Resim 2, 100 puan Resim 4, 100 puan Resim 10, 100 puan


3

Görüntü enerjisini nokta ağırlıklı bir harita olarak kullanmak

Bu zorluğa olan yaklaşımımda, belirli bir görüntü alanının "alaka düzeyini" belirli bir noktanın Voronoi centroid olarak seçilme ihtimaliyle eşleştirmenin bir yolunu istedim. Ancak, Voronoi mozaiklemesinin sanatsal hissini, rastgele imge noktaları seçerek korumak istedim. Ek olarak, büyük görüntüler üzerinde çalışmak istedim, bu nedenle aşağı örnekleme sürecinde hiçbir şey kaybetmiyorum. Algoritmam kabaca şöyle:

  1. Her görüntü için bir netlik haritası oluşturun. Keskinlik haritası normalleştirilmiş görüntü enerjisi (veya görüntünün yüksek frekans sinyalinin karesi) ile tanımlanır. Bir örnek şuna benziyor:

Keskinlik haritası

  1. Netlik haritasındaki noktalardan yüzde 70 ve diğer tüm noktalardan yüzde 30 alarak görüntüden bir miktar nokta oluşturun. Bu, görüntünün yüksek ayrıntı kısımlarından noktaların daha yoğun bir şekilde örneklendiği anlamına gelir.
  2. Renk!

Sonuçlar

N = 100, 500, 1000, 3000

Resim 1, N = 100 Resim 1, N = 500 Resim 1, N = 1000 Resim 1, N = 3000

Resim 2, N = 100 Resim 2, N = 500 Resim 2, N = 1000 Resim 2, N = 3000

Resim 3, N = 100 Resim 3, N = 500 Resim 3, N = 1000 Resim 3, N = 3000

Resim 4, N = 100 Resim 4, N = 500 Resim 4, N = 1000 Resim 4, N = 3000

Resim 5, N = 100 Resim 5, N = 500 Resim 5, N = 1000 Resim 5, N = 3000

Resim 6, N = 100 Resim 6, N = 500 Resim 6, N = 1000 Resim 6, N = 3000

Resim 7, N = 100 Resim 7, N = 500 Resim 7, N = 1000 Resim 7, N = 3000

Resim 8, N = 100 Resim 8, N = 500 Resim 8, N = 1000 Resim 8, N = 3000

Resim 9, N = 100 Resim 9, N = 500 Resim 9, N = 1000 Resim 9, N = 3000

Resim 10, N = 100 Resim 10, N = 500 Resim 10, N = 1000 Resim 10, N = 3000

Resim 11, N = 100 Resim 11, N = 500 Resim 11, N = 1000 Resim 11, N = 3000

Resim 12, N = 100 Resim 12, N = 500 Resim 12, N = 1000 Resim 12, N = 3000

Resim 13, N = 100 Resim 13, N = 500 Resim 13, N = 1000 Resim 13, N = 3000

Resim 14, N = 100 Resim 14, N = 500 Resim 14, N = 1000 Resim 14, N = 3000


14
A) bunu oluşturmak için kullanılan kaynak kodu dahil etmek ve b) her bir küçük resmi tam boyutlu görüntüye bağlamak?
Martin Ender
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.