वोरोनोई मानचित्र के रूप में एक चित्र बनाएं


170

मेरे चुनौती विचार को सही दिशा में ले जाने के लिए केल्विन के शौक का श्रेय।

विमान में बिंदुओं के एक सेट पर विचार करें, जिसे हम साइट कहेंगे , और प्रत्येक साइट के साथ एक रंग जोड़ेंगे। अब आप प्रत्येक बिंदु को निकटतम साइट के रंग के साथ रंगकर पूरे विमान को पेंट कर सकते हैं। इसे वोरोनोई मानचित्र (या वोरोनोई आरेख ) कहा जाता है । सिद्धांत रूप में, वोरोनोई मानचित्रों को किसी भी दूरी मीट्रिक के लिए परिभाषित किया जा सकता है, लेकिन हम बस सामान्य यूक्लिडियन दूरी का उपयोग करेंगे r = √(x² + y²)( ध्यान दें: आपको इस चुनौती में प्रतिस्पर्धा करने के लिए इनमें से किसी एक की गणना और रेंडर करने की आवश्यकता नहीं है।)

यहां 100 साइटों के साथ एक उदाहरण दिया गया है:

यहां छवि विवरण दर्ज करें

यदि आप किसी सेल को देखते हैं, तो उस सेल के सभी बिंदु किसी अन्य साइट की तुलना में संबंधित साइट के करीब हैं।

आपका कार्य इस तरह के वोरोनोई मानचित्र के साथ किसी दिए गए चित्र को अनुमानित करना है। आपको किसी भी सुविधाजनक रेखापुंज ग्राफिक्स प्रारूप में छवि दी गई है, साथ ही एक पूर्णांक एन । फिर आपको एन साइटों तक उत्पादन करना चाहिए , और प्रत्येक साइट के लिए एक रंग, जैसे कि इन साइटों पर आधारित वोरोनोई मानचित्र इनपुट छवि को यथासंभव निकटता से देखता है।

आप अपने आउटपुट से वोरोनोई मानचित्र प्रस्तुत करने के लिए इस चुनौती के निचले भाग में स्टैक स्निपेट का उपयोग कर सकते हैं, या यदि आप चाहें तो इसे स्वयं प्रस्तुत कर सकते हैं।

आप कर सकते हैं में निर्मित या तीसरे पक्ष के कार्यों का उपयोग साइटों का एक सेट से एक Voronoi नक्शा गणना करने के लिए (यदि आप की जरूरत है)।

यह एक लोकप्रियता प्रतियोगिता है, इसलिए सबसे शुद्ध वोटों के साथ जवाब जीत जाता है। मतदाताओं को जवाब देने के लिए प्रोत्साहित किया जाता है

  • मूल चित्र और उनके रंग कितने अच्छे हैं।
  • एल्गोरिथ्म विभिन्न प्रकार की छवियों पर कितनी अच्छी तरह काम करता है।
  • एल्गोरिथ्म छोटे एन के लिए कितना अच्छा काम करता है ।
  • क्या एल्गोरिथ्म अनुकूल रूप से क्लस्टर छवि के क्षेत्रों में इंगित करता है जिसे अधिक विस्तार की आवश्यकता होती है।

परीक्षण छवियाँ

यहां आपके एल्गोरिथ्म पर परीक्षण करने के लिए कुछ छवियां हैं (हमारे कुछ सामान्य संदिग्ध, कुछ नए)। बड़े संस्करणों के लिए चित्रों पर क्लिक करें।

महान लहर कांटेदार जंगली चूहा समुद्र तट कॉर्नेल शनि ग्रह भूरे भालू योशी एक प्रकार का बंदर केकड़ा नेबुला भूतों का बच्चा झरना चीख

पहली पंक्ति में समुद्र तट ओलिविया बेल द्वारा खींचा गया था , और उसकी अनुमति के साथ शामिल किया गया था।

यदि आप एक अतिरिक्त चुनौती चाहते हैं, तो योशी को एक सफेद पृष्ठभूमि के साथ आज़माएं और अपनी पेट रेखा को सही से प्राप्त करें।

आप इस imgur गैलरी में इन सभी परीक्षण छवियों को पा सकते हैं जहाँ आप इन सभी को ज़िप फ़ाइल के रूप में डाउनलोड कर सकते हैं। एल्बम में एक और परीक्षण के रूप में एक यादृच्छिक वोरोनोई आरेख भी है। संदर्भ के लिए, यहां वह डेटा है जिसने इसे बनाया है

कृपया विभिन्न छवियों और एन की एक किस्म के लिए उदाहरण आरेखों को शामिल करें, उदाहरण के लिए 100, 300, 1000, 3000 (साथ ही संबंधित सेल विनिर्देशों में से कुछ के लिए pastebins)। आप कोशिकाओं के बीच काले किनारों का उपयोग या छोड़ सकते हैं जैसा कि आप फिट देखते हैं (यह दूसरों की तुलना में कुछ छवियों पर बेहतर दिख सकता है)। हालांकि साइटों को शामिल न करें (एक अलग उदाहरण को छोड़कर शायद अगर आप यह बताना चाहते हैं कि आपकी साइट प्लेसमेंट कैसे काम करती है)।

यदि आप बड़ी संख्या में परिणाम दिखाना चाहते हैं, तो आप जवाबों के आकार को उचित रखने के लिए imgur.com पर एक गैलरी बना सकते हैं । वैकल्पिक रूप से, अपनी पोस्ट में थंबनेल डालें और उन्हें बड़ी छवियों के लिंक बनाएं, जैसे मैंने अपने संदर्भ उत्तर में किया था । आप simgur.com लिंक (जैसे I3XrT.png-> I3XrTs.png) में फ़ाइल नाम में संलग्न करके छोटे थंबनेल प्राप्त कर सकते हैं । इसके अलावा, यदि आपको कुछ अच्छा लगता है, तो अन्य परीक्षण छवियों का उपयोग करने के लिए स्वतंत्र महसूस करें।

रेंडरर

अपने परिणामों को प्रस्तुत करने के लिए अपने उत्पादन को निम्नलिखित स्टैक स्निपेट में पेस्ट करें। के रूप में प्रत्येक कोशिका के क्रम में 5 चल बिन्दु संख्या द्वारा निर्दिष्ट किया जाता सटीक सूची प्रारूप, अप्रासंगिक है जब तक x y r g bहै, जहां xऔर yसेल की साइट के निर्देशांक हैं, और r g bरेंज में लाल, हरे और नीले रंग चैनल हैं 0 ≤ r, g, b ≤ 1

स्निपेट सेल किनारों की एक लाइन चौड़ाई निर्दिष्ट करने के लिए विकल्प प्रदान करता है, और सेल साइटों को दिखाया जाना चाहिए या नहीं (उत्तरार्द्ध ज्यादातर डिबगिंग उद्देश्यों के लिए)। लेकिन ध्यान दें कि सेल विनिर्देशों में परिवर्तन होने पर आउटपुट केवल फिर से प्रदान किया जाता है - इसलिए यदि आप कुछ अन्य विकल्पों को संशोधित करते हैं, तो कोशिकाओं या कुछ के लिए एक स्थान जोड़ें।

यह वास्तव में अच्छा जेएस वोरोनोई पुस्तकालय लिखने के लिए रेमंड हिल के लिए बड़े पैमाने पर क्रेडिट ।

संबंधित चुनौतियां


5
@frogeyedpeas आपको मिलने वाले वोटों को देखकर। ;) यह एक लोकप्रियता प्रतियोगिता है। आवश्यक रूप से सबसे अच्छा तरीका नहीं है। विचार यह है कि आप इसे जितना हो सके उतना करने की कोशिश करते हैं, और वोट प्रतिबिंबित करेंगे कि क्या लोग सहमत हैं कि आपने अच्छा काम किया है। इन में एक निश्चित मात्रा में विषय-वस्तु है। संबंधित चुनौतियों मैं जुड़ा हुआ है, या पर एक नजर डालें इस एक पर । आप देखेंगे कि आम तौर पर कई तरह के दृष्टिकोण होते हैं लेकिन मतदान प्रणाली बेहतर समाधानों को ऊपर तक बुलबुला बनाने में मदद करती है और एक विजेता का फैसला करती है।
मार्टिन एंडर

3
ओलिविया ने अपने समुद्र तट के सन्निकटन को मंजूरी दे दी जो इस प्रकार अब तक प्रस्तुत है।
एलेक्स ए।

3
@AlexA। डेवोन ने अपने चेहरे के कुछ अंदाजों को इस प्रकार प्रस्तुत किया। वह n = 100 संस्करणों में से किसी का भी बड़ा प्रशंसक नहीं है;)
जियोबिट्स

1
@Geobits: जब वह बड़े होंगे तब वह समझेंगे।
एलेक्स ए

1
यहाँ एक पृष्ठ पर केन्द्रित वोरोनोई-आधारित स्टीपलिंग तकनीक है । प्रेरणा का एक अच्छा स्रोत (संबंधित मास्टर थीसिस एल्गोरिथ्म में संभावित सुधार की एक अच्छी चर्चा है)।
जॉब

जवाबों:


112

अजगर + scipy + scikit छवि , भारित प्वासों डिस्क नमूना

मेरा समाधान बल्कि जटिल है। मैं शोर को दूर करने के लिए छवि पर कुछ प्रीप्रोसेसिंग करता हूं और एक मैपिंग प्राप्त करता हूं कि प्रत्येक बिंदु कितना 'दिलचस्प' है (स्थानीय एन्ट्रॉपी और एज डिटेक्शन के संयोजन का उपयोग करके):

फिर मैं एक मोड़ के साथ पॉइसन डिस्क नमूने का उपयोग करके नमूने बिंदुओं का चयन करता हूं : सर्कल की दूरी हमारे द्वारा निर्धारित वजन से निर्धारित होती है।

एक बार जब मेरे पास नमूने बिंदु होते हैं तो मैं छवि को वोरोनोई खंडों में विभाजित करता हूं और प्रत्येक खंड के प्रत्येक खंड के अंदर एल * ए * बी * रंग मूल्यों का औसत असाइन करता हूं।

मेरे पास बहुत सारे आंकड़े हैं, और मुझे यह भी सुनिश्चित करने के लिए गणित का थोड़ा सा करना चाहिए कि नमूना अंकों की संख्या करीब है 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!")

इमेजिस

क्रमशः N100, 300, 1000 और 3000 है:

एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी
एबीसी एबीसी एबीसी एबीसी


2
मुझे यह पसंद हे; यह स्मोक्ड ग्लास की तरह दिखता है।
BobTheAwesome

3
मैंने इसे थोड़ा सा गड़बड़ कर दिया है, और आप बेहतर परिणाम प्राप्त करते हैं, विशेष रूप से कम त्रिकोण छवियों के लिए, यदि आप denoise_bilatteral को एक denoise_tv_bregman से बदलते हैं। यह अपने संप्रदाय में और भी अधिक पैच उत्पन्न करता है, जो मदद करता है।
LKlevin

@LKlevin आपने किस वजन का उपयोग किया?
orlp

मैंने वजन के रूप में 1.0 का उपयोग किया।
LKlevin

65

सी ++

मेरा दृष्टिकोण काफी धीमा है, लेकिन मैं परिणामों की गुणवत्ता से बहुत खुश हूं जो यह देता है, विशेष रूप से किनारों को संरक्षित करने के संबंध में। उदाहरण के लिए, यहां योशी और कॉर्नेल बॉक्स के साथ सिर्फ 1000 साइटें हैं:

दो मुख्य भाग हैं जो इसे टिक करते हैं। evaluate()फ़ंक्शन में सन्निहित, पहला, उम्मीदवार साइट स्थानों का एक सेट लेता है, उन पर इष्टतम रंग सेट करता है और टारगेट किए गए वोरोनोई टसेसेलेशन बनाम लक्ष्य छवि के पीएसएनआर के लिए एक अंक देता है । प्रत्येक साइट के रंगों को साइट के चारों ओर सेल द्वारा कवर औसत लक्ष्य छवि पिक्सल द्वारा निर्धारित किया जाता है। मैं प्रत्येक सेल के लिए सबसे अच्छा रंग और परिणामस्वरूप PSNR दोनों की गणना करने में मदद करने के लिए Welford के एल्गोरिथ्म का उपयोग करता हूं , विचरण, MSE और PSNR के बीच संबंध का उपयोग करके छवि पर केवल एक पास का उपयोग करके। यह विशेष रूप से रंग के संबंध में साइट स्थानों का सबसे अच्छा सेट खोजने में से एक को समस्या को कम करता है।

दूसरे भाग में main(), इस सेट को खोजने की कोशिश की जाती है। यह यादृच्छिक पर बिंदुओं का एक सेट चुनकर शुरू होता है। फिर, प्रत्येक चरण में यह एक बिंदु (राउंड-रॉबिन) को हटाता है और इसे बदलने के लिए यादृच्छिक उम्मीदवार बिंदुओं के एक सेट का परीक्षण करता है। जो गुच्छा के उच्चतम PSNR पैदावार को स्वीकार करता है और रखा जाता है। प्रभावी रूप से, यह साइट को एक नए स्थान पर कूदने का कारण बनता है और आमतौर पर बिट-बाय-बिट को बेहतर बनाता है। ध्यान दें कि एल्गोरिथ्म जानबूझकर उम्मीदवार के रूप में मूल स्थान को बरकरार नहीं रखता है। कभी-कभी इसका मतलब है कि कूद पूरी छवि की गुणवत्ता को कम करती है। ऐसा करने से स्थानीय मैक्सिमा में फंसने से बचने में मदद मिलती है। यह एक रोक मापदंड भी देता है; अब तक पाए गए साइटों के सर्वश्रेष्ठ सेट पर सुधार के बिना कुछ निश्चित चरणों में जाने के बाद कार्यक्रम समाप्त हो गया।

ध्यान दें कि यह कार्यान्वयन काफी बुनियादी है और आसानी से सीपीयू-कोर समय के घंटे ले सकता है, खासकर जब साइट की संख्या बढ़ती है। यह प्रत्येक उम्मीदवार के लिए पूर्ण वोरोनोई मानचित्र को फिर से प्रदर्शित करता है और ब्रूट बल प्रत्येक पिक्सेल के लिए सभी साइटों की दूरी का परीक्षण करता है। चूंकि प्रत्येक ऑपरेशन में एक समय में एक बिंदु को निकालना और दूसरे को जोड़ना शामिल होता है, इसलिए प्रत्येक चरण पर छवि में वास्तविक परिवर्तन काफी स्थानीय होने वाले हैं। वोरोनोई आरेख को कुशलतापूर्वक अपडेट करने के लिए एल्गोरिदम हैं और मुझे विश्वास है कि वे इस एल्गोरिथ्म को एक जबरदस्त गति प्रदान करेंगे। इस प्रतियोगिता के लिए, हालांकि, मैंने चीजों को सरल और पाशविक बल रखने के लिए चुना है।

कोड

#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 का उपयोग करता हूं , हालांकि GIMP और काफी कुछ अन्य प्रोग्राम भी कर सकते हैं।

इसे संकलित करने के लिए, प्रोग्राम को इस रूप में सहेजें voronoi.cppऔर फिर चलाएं:

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

मुझे उम्मीद है कि यह शायद विजुअल स्टूडियो के हाल के संस्करणों के साथ विंडोज पर काम करेगा, हालांकि मैंने यह कोशिश नहीं की है। आप यह सुनिश्चित करना चाहते हैं कि आप C ++ 11 के साथ संकलन कर रहे हैं या बेहतर और OpenMP सक्षम हैं यदि आप करते हैं। ओपनएमपी कड़ाई से आवश्यक नहीं है, लेकिन यह निष्पादन के समय को अधिक सहनीय बनाने में बहुत मदद करता है।

इसे चलाने के लिए, कुछ ऐसा करें:

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

बाद में फ़ाइल को साइट डेटा के साथ अपडेट किया जाएगा। पहली पंक्ति में छवि की चौड़ाई और ऊँचाई होगी, इसके बाद x, y, r, g, b मानों की रेखाएँ होंगी जो समस्या वर्णन में जावास्क्रिप्ट रेंडरर में कॉपी और पेस्ट करने के लिए उपयुक्त हैं।

कार्यक्रम के शीर्ष पर तीन स्थिरांक आपको गति बनाम गुणवत्ता के लिए इसे ट्यून करने की अनुमति देते हैं। decimationकारक है जब रंग और PSNR के लिए साइटों का एक सेट का मूल्यांकन लक्ष्य छवि coarsens। यह जितना अधिक होगा, कार्यक्रम उतनी ही तेजी से चलेगा। इसे 1 पर सेट करना पूर्ण रिज़ॉल्यूशन छवि का उपयोग करता है। candidatesलगातार नियंत्रण कितने उम्मीदवारों हर कदम पर परीक्षण करने के लिए। उच्च कूदने के लिए एक अच्छा स्थान खोजने का एक बेहतर मौका देता है, लेकिन कार्यक्रम को धीमा कर देता है। अंत में, terminationयह है कि कार्यक्रम कितने चरणों में अपने उत्पादन में सुधार करने से पहले जा सकता है। इसे बढ़ाने से बेहतर परिणाम मिल सकते हैं, लेकिन इसे थोड़ा और लंबा कर सकते हैं।

इमेजिस

N = 100, 300, 1000 और 3000:


1
यह IMO जीतना चाहिए था - मेरी तुलना में बेहतर।
orlp

1
@orlp - धन्यवाद! हालांकि निष्पक्ष होने के लिए, आपने बहुत जल्द अपना पोस्ट किया और यह बहुत अधिक तेज़ी से चलता है। गति मायने रखती है!
बोजुम

1
खैर, मेरा वास्तव में एक वोरोनोई मानचित्र उत्तर नहीं है :) यह वास्तव में एक अच्छा नमूना एल्गोरिथ्म है, लेकिन वोरोनोई साइटों में नमूना बिंदुओं को बदलना स्पष्ट रूप से इष्टतम नहीं है।
orlp

55

आईडीएल, अनुकूली शोधन

यह विधि खगोलीय सिमुलेशन, और उपखंड सतह से अनुकूली जाल शोधन से प्रेरित है । यह उस तरह का कार्य है, जिस पर IDL खुद को प्रूव करता है, जिसे आप बड़ी संख्या में बिलियन फंक्शंस द्वारा बता पाएंगे, जिनका मैं उपयोग करने में सक्षम था। : डी

मैंने काली पृष्ठभूमि वाले योशी परीक्षण चित्र के लिए कुछ इंटरमीडिएट का आउटपुट दिया है n = 1000

सबसे पहले, हम छवि (उपयोग ct_luminance) पर एक ल्यूमिनेंस ग्रीसेकेल करते हैं , और अच्छी बढ़त का पता लगाने के लिए प्रीविट फिल्टर ( prewitt, विकिपीडिया देखें ) लागू करते हैं :

एबीसी एबीसी

फिर असली ग्रन्ट-वर्क आता है: हम छवि को 4 में विभाजित करते हैं, और फ़िल्टर छवि में प्रत्येक चतुर्थांश में विचरण को मापते हैं। हमारे विचरण को उपखंड के आकार से भारित किया जाता है (जो इस पहले चरण में बराबर है), ताकि उच्च विचरण वाले "नुकीले" क्षेत्र उप-विभाजित छोटे और छोटे और छोटे न हों। फिर, हम अधिक विस्तार के साथ उपविभागों को लक्षित करने के लिए भारित विचरण का उपयोग करते हैं, और प्रत्येक विस्तार से समृद्ध अनुभाग को 4 अतिरिक्त में क्रमिक रूप से उपविभाजित करते हैं, जब तक कि हम अपने लक्ष्य संख्या साइटों (प्रत्येक उपखंड में ठीक एक साइट शामिल नहीं करते हैं)। जब से हम पुनरावृति के लिए हर बार 3 साइट जोड़ रहे हैं, हम n - 2 <= N <= nसाइटों के साथ समाप्त होते हैं।

मैंने इस छवि के लिए उपखंड प्रक्रिया का एक .webm बनाया, जिसे मैं एम्बेड नहीं कर सकता, लेकिन यह यहाँ है ; प्रत्येक उपधारा में रंग भारित विचरण द्वारा निर्धारित किया जाता है। (मैंने व्हाइट-बैकग्राउंड योशी के लिए एक ही तरह का वीडियो बनाया, तुलना के लिए, रंग की मेज उलट गई, इसलिए यह काले के बजाय सफेद की ओर जाता है। यह यहां है ।) उपखंड का अंतिम उत्पाद इस तरह दिखता है:

एबीसी

एक बार जब हमारे पास उपखंडों की हमारी सूची होती है, तो हम प्रत्येक उपखंड से गुजरते हैं। अंतिम साइट का स्थान प्रीविट छवि के न्यूनतम की स्थिति है, अर्थात, कम से कम "नुकीला" पिक्सेल, और अनुभाग का रंग उस पिक्सेल का रंग है; यहां एक मूल छवि है, जिसमें चिह्नित साइट हैं:

एबीसी

फिर, हम अपने संबंधित रंग में एक छवि बफर के लिए प्रत्येक बहुभुज को खींचने से पहले, triangulateसाइटों के डेलॉनाय त्रिभुज की गणना voronoiकरने के लिए और प्रत्येक वोरोनोई बहुभुज के कोने को परिभाषित करने के लिए बिल्ट-इन का उपयोग करते हैं। अंत में, हम छवि बफर के एक स्नैपशॉट को बचाते हैं।

एबीसी

कोड:

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 और 1000 साइटों के साथ चली।

आउटपुट यहां एकत्र किया गया है , और यहां कुछ थंबनेल हैं:

n = 100

एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी

n = 500

एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी

n = 1000

एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी एबीसी


9
मैं वास्तव में इस तथ्य को पसंद करता हूं कि यह समाधान अधिक जटिल क्षेत्रों में अधिक अंक डालता है, जो मुझे लगता है कि इरादा है, और इसे इस बिंदु पर दूसरों से अलग करता है।
अलेक्जेंडर-ब्रेट

हाँ, डिटेल-
ग्रुप्ड पॉइंट्स

3
बहुत साफ व्याख्या, और चित्र प्रभावशाली हैं! मेरा एक सवाल है - ऐसा लगता है कि योशी जब एक सफेद पृष्ठभूमि पर होती है, तो आपको बहुत अलग-अलग छवियां मिलती हैं, जहां हमारे पास कुछ विषम आकार होते हैं। वह क्या कारण हो सकता है?
ब्रेनसटेल

2
@BrianSteel मुझे लगता है कि रूपरेखा को उच्च-विचरण क्षेत्रों के रूप में उठाया जाता है और अनावश्यक रूप से ध्यान केंद्रित किया जाता है, और फिर वास्तव में उच्च-विस्तार वाले क्षेत्रों के कारण कम अंक दिए जाते हैं।
doppelgreener

@BrainSteel मुझे लगता है कि डोपल सही है - काली सीमा और सफेद पृष्ठभूमि के बीच एक मजबूत बढ़त है, जो एल्गोरिथ्म में बहुत विस्तार के लिए पूछता है। मुझे यकीन नहीं है कि यह कुछ ऐसा है जो मैं (या, अधिक महत्वपूर्ण बात, ठीक करना चाहिए ) ...
उपसंहार

47

पायथन 3 + पीआईएल + साइपी, फजी के-साधन

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- का मतलब है प्राकृतिक रूप से विभाजन को वोरोनोई कोशिकाओं में विभाजित करना, क्योंकि अंक निकटतम सेंट्रोइड से जुड़े होते हैं। हालांकि, हमें किसी तरह से रंगों को एक बाधा के रूप में जोड़ने की आवश्यकता है।

बेहतर रंग हेरफेर के लिए पहले हम प्रत्येक पिक्सेल को लैब कलर स्पेस में बदलते हैं।

फिर हम "गरीब आदमी की बढ़त का पता लगाने" की तरह करते हैं। प्रत्येक पिक्सेल के लिए, हम इसके ऑर्थोगोनल और विकर्ण पड़ोसियों को देखते हैं, और रंग में माध्य-वर्ग अंतर की गणना करते हैं। फिर हम इस अंतर के द्वारा सभी पिक्सेल को क्रमबद्ध करते हैं, सूची के मोर्चे पर अपने पड़ोसियों के समान पिक्सेल के साथ, और सबसे पीछे उनके पड़ोसियों के लिए सबसे अधिक भिन्नता है (यानी, एक किनारे बिंदु होने की संभावना है)। यहाँ ग्रह के लिए एक उदाहरण दिया गया है, जहाँ पिक्सेल जितना चमकीला होता है, उतना ही उसके पड़ोसियों से अलग होता है:

यहां छवि विवरण दर्ज करें

(ऊपर दिए गए आउटपुट पर एक स्पष्ट ग्रिड जैसा पैटर्न है। @randomra के अनुसार, यह संभवतः हानिपूर्ण JPG एन्कोडिंग या छवियों को संकुचित करने वाले imgur के कारण है।)

अगला, हम इस पिक्सेल का उपयोग करके बड़ी संख्या में बिंदुओं को क्लस्टर करने के लिए ऑर्डर करते हैं। हम एक घातांक वितरण का उपयोग करते हैं, जो उन बिंदुओं को प्राथमिकता देते हैं जो अधिक किनारे वाले और "दिलचस्प" हैं।

यहां छवि विवरण दर्ज करें

क्लस्टरिंग के लिए, हम पहले Nसेंट्रोइड्स लेते हैं , बेतरतीब ढंग से ऊपर के समान घातांक वितरण का उपयोग करके चुना जाता है। एक प्रारंभिक पुनरावृत्ति निष्पादित की जाती है, और परिणामी समूहों में से प्रत्येक के लिए हम एक मतलब रंग और एक रंग विचरण सीमा प्रदान करते हैं। फिर कई पुनरावृत्तियों के लिए, हम:

  • सेंट्रोइड्स के डेलुनाय त्रिभुज का निर्माण करें, ताकि हम आसानी से पड़ोसियों को सेंट्रोइड्स के लिए क्वेरी कर सकें।
  • सेंट्रोइड्स को हटाने के लिए त्रिकोणासन का उपयोग करें जो कि उनके पड़ोसियों और पड़ोसी के पड़ोसियों के संयुक्त रंग में सबसे अधिक (> 4/5) के करीब हैं। कोई भी संबद्ध नमूना बिंदु भी हटा दिए जाते हैं, और नए प्रतिस्थापन सेंट्रोइड और नमूना बिंदु जोड़े जाते हैं। यह चरण एल्गोरिदम को अधिक क्लस्टर रखने के लिए मजबूर करने की कोशिश करता है जहां विस्तार की आवश्यकता होती है।
  • नए सेंट्रोइड्स के लिए एक केडी-ट्री का निर्माण करें, ताकि हम किसी भी नमूना बिंदु पर निकटतम सेंट्रोइड्स को आसानी से क्वेरी कर सकें।
  • 6 निकटतम सेंट्रोइड्स में से एक के लिए प्रत्येक नमूना बिंदु को असाइन करने के लिए पेड़ का उपयोग करें (6 अनुभवजन्य रूप से चुना गया)। एक सेंट्रोइड केवल एक नमूना बिंदु को स्वीकार करेगा यदि बिंदु का रंग सेंट्रोइड के रंग विचरण सीमा के भीतर है। हम प्रत्येक नमूना बिंदु को पहले स्वीकार करने वाले केंद्रक को सौंपने की कोशिश करते हैं, लेकिन यदि यह संभव नहीं है, तो हम बस निकटतम सेंट्रोइड को असाइन करते हैं। एल्गोरिथ्म का "फ़िज़नेस" इस कदम से आता है, क्योंकि यह क्लस्टर के लिए ओवरलैप करना संभव है।
  • केन्द्रक को पुनः संयोजित करें।

यहां छवि विवरण दर्ज करें

(पूर्ण आकार के लिए क्लिक करें)

अंत में, हम एक समान वितरण का उपयोग करके, इस बार बड़ी संख्या में अंक का नमूना लेते हैं। एक और केडी-ट्री का उपयोग करते हुए, हम प्रत्येक बिंदु को इसके निकटतम सेंट्रोइड को सौंपते हैं, जिससे गुच्छे बनते हैं। हम तब अपने अंतिम सेल रंगों (@PhiNotPi और @ MartinBüttner के लिए इस कदम के लिए विचार) को देते हुए एक पहाड़ी-चढ़ाई एल्गोरिथ्म का उपयोग करते हुए प्रत्येक क्लस्टर के मध्य रंग को अनुमानित करते हैं ।

यहां छवि विवरण दर्ज करें

टिप्पणियाँ

स्निपेट ( OUTFILE) के लिए एक टेक्स्ट फ़ाइल को आउटपुट करने के अलावा , यदि प्रोग्राम पर DEBUGसेट किया Trueगया है , तो आउटपुट भी होगा और ऊपर की छवियों को अधिलेखित करेगा। एल्गोरिथ्म में प्रत्येक छवि के लिए कुछ मिनट लगते हैं, इसलिए यह प्रगति पर जाँच का एक अच्छा तरीका है जो रनिंग टाइम में एक बहुत कुछ नहीं जोड़ता है।

नमूना आउटपुट

एन = 32:

यहां छवि विवरण दर्ज करें

एन = 100:

यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें

एन = 1000:

यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें

एन = 3000:

यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें


1
मुझे वास्तव में पसंद है कि आपका ऑन-व्हाइट योशिस कितना अच्छा निकला।
अधिकतम

26

गणितज्ञ, रैंडम सेल

यह आधारभूत समाधान है, इसलिए आपको मेरे द्वारा पूछे जाने वाले न्यूनतम का एक विचार मिलता है। में fileऔर एन में फ़ाइल नाम (स्थानीय रूप से या URL के रूप में) को देखते हुए n, निम्नलिखित कोड बस एन यादृच्छिक पिक्सल को चुनता है , और उन पिक्सेल में पाए जाने वाले रंगों का उपयोग करता है। यह वास्तव में भोला है और अविश्वसनीय रूप से अच्छी तरह से काम नहीं करता है, लेकिन मैं चाहता हूं कि आप लोग इसे हरा दें। :)

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

यहाँ N = 100 के लिए सभी परीक्षण चित्र दिए गए हैं (सभी चित्र बड़े संस्करणों के लिए लिंक हैं):

यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें

जैसा कि आप देख सकते हैं, ये अनिवार्य रूप से बेकार हैं। जबकि उनके पास कुछ कलात्मक मूल्य हो सकता है, एक अभिव्यक्तिवादी तरीके से, मूल छवियां मुश्किल से पहचानने योग्य हैं।

के लिए एन = 500 , स्थिति थोड़ा सुधार हुआ है, लेकिन अभी भी बहुत अजीब कलाकृतियों हैं, छवियों बाहर धोया देखो, और कोशिकाओं का एक बहुत विस्तार के बिना क्षेत्रों पर बर्बाद कर रहे हैं:

यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें यहां छवि विवरण दर्ज करें

तुम्हारी बारी!


मैं एक अच्छा कोडर नहीं हूं, लेकिन मेरे भगवान ये चित्र सुंदर दिख रहे हैं। गजब का विचार!
फराज मसरूर

इसका कोई कारण Dimensions@ImageDataऔर नहीं ImageDimensions? आप धीमी गति ImageDataसे उपयोग करके पूरी तरह से बच सकते हैं PixelValue
२०१२ आर्कम्पियन १।

@ 2012 आर्केड कोई कारण नहीं है, मुझे अभी पता नहीं था कि फंक्शन मौजूद है। मैं बाद में इसे ठीक कर सकता हूं और अनुशंसित एन-मानों के उदाहरण चित्र भी बदल सकता हूं।
मार्टिन एंडर

23

मेथेमेटिका

हम सभी जानते हैं कि मार्टिन मैथमेटिका से प्यार करता है इसलिए मुझे इसे मैथमैटिक के साथ आजमाएं।

मेरा एल्गोरिथ्म एक प्रारंभिक voronoi आरेख बनाने के लिए छवि किनारों से यादृच्छिक बिंदुओं का उपयोग करता है। आरेख को फिर एक सरल माध्य फ़िल्टर के साथ मेष के पुनरावृत्त समायोजन द्वारा पूर्वनिर्धारित किया जाता है। यह उच्च विपरीत क्षेत्रों के पास उच्च सेल घनत्व और पागल कोणों के बिना नेत्रहीन सुखदायक कोशिकाओं के साथ छवियां देता है।

निम्नलिखित छवियां कार्रवाई में प्रक्रिया का एक उदाहरण दिखाती हैं। मज़ा कुछ हद तक मैथेमेटिका के खराब एंटीलियासिंग द्वारा खराब हो गया है, लेकिन हमें वेक्टर ग्राफिक्स मिलते हैं, जो कुछ के लायक होना चाहिए।

यादृच्छिक नमूने के बिना यह एल्गोरिथ्म, यहां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]}]]
   ];

पहली पोस्ट के लिए अच्छा काम! :) आप 32 कोशिकाओं के साथ वोरोनोई परीक्षण छवि की कोशिश करना चाह सकते हैं (यह छवि में कोशिकाओं की संख्या है)।
मार्टिन एंडर

धन्यवाद! मुझे लगता है कि मेरा एल्गोरिथ्म इस उदाहरण पर बहुत अच्छा प्रदर्शन करेगा। ); बीज सेल किनारों पर प्रारंभ होगा मिल और प्रत्यावर्तन यह बहुत बेहतर नहीं होगा
पंजा

मूल छवि में परिवर्तित करने के लिए धीमी दर के बावजूद, मुझे लगता है कि आपके एल्गोरिथ्म में बहुत कलात्मकता का परिणाम है! (Georges Seurat के सुधार संस्करण की तरह काम करता है)। अच्छा काम!
neizod

आप अपनी अंतिम पंक्तियों को बदलकर आकर्षक दिखने वाले बहुभुज रंगों को भी प्राप्त कर सकते हैंGraphics@Table[ Append[mp, VertexColors -> RGBColor /@ ImageValue[img, First[mp]]], {mp, MeshPrimitives[voronoiRelaxed, 2]}]
हिस्टोग्राम्स

13

पायथन + SciPy + emcee

मैंने जो एल्गोरिथ्म उपयोग किया है वह निम्नलिखित है:

  1. एक छोटे आकार में छवियों का आकार बदलें (~ 150 पिक्सेल)
  2. अधिकतम चैनल मानों की एक अनछुई-मुखौटे वाली छवि बनाएं (यह सफेद क्षेत्रों को भी दृढ़ता से लेने में मदद नहीं करता है)।
  3. पूर्ण मूल्य लें।
  4. इस छवि के आनुपातिक के साथ यादृच्छिक बिंदु चुनें। यह बिंदुओं को या तो अलग करता है।
  5. एक लागत फ़ंक्शन कम करने के लिए चुने गए बिंदुओं को परिष्कृत करें। फ़ंक्शन चैनलों में चुकता विचलन के योग की अधिकतम राशि है (फिर से ठोस रंग और न केवल ठोस सफेद के लिए पूर्वाग्रह की मदद)। मैंने मार्को चैन मोंटे कार्लो को एक आशावादी के रूप में इमीज़ मॉड्यूल (अत्यधिक अनुशंसित) के साथ दुरुपयोग किया है। जब एन श्रृंखला पुनरावृत्तियों के बाद कोई नया सुधार नहीं पाया जाता है तो यह प्रक्रिया समाप्त हो जाती है।

एल्गोरिथ्म बहुत अच्छी तरह से काम करता है। दुर्भाग्य से यह केवल समझदारी से छोटी छवियों पर चल सकता है। मेरे पास वोरोनोई अंक लेने और उन्हें बड़ी छवियों पर लागू करने का समय नहीं है। उन्हें इस बिंदु पर परिष्कृत किया जा सकता था। मैं भी बेहतर minima पाने के लिए MCMC चला सकता था। एल्गोरिथ्म का कमजोर बिंदु यह है कि यह महंगा है। मेरे पास १००० अंकों से आगे बढ़ने का समय नहीं है और १००० बिंदु चित्रों के एक जोड़े को अभी भी परिष्कृत किया जा रहा है।

(बड़ा संस्करण पाने के लिए राइट क्लिक करें और इमेज देखें)

100, 300 और 1000 अंकों के लिए थंबनेल

बड़े संस्करणों के लिंक http://imgur.com/a/2IXDT#9 (100 अंक), http://imgur.com/a/bBQ7q (300 अंक) और http://imgur.com/a/rr8wJ हैं (1000 अंक)

#!/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 से अधिक)

अशक्त नकाबपोश शनि छवि

यदि मुझे अधिक समय मिलता है तो मैं बड़ी छवियों और वोरोनोई बिंदुओं को पोस्ट करूंगा।

संपादित करें: यदि आप वॉकरों की संख्या को 100 * तक बढ़ा देते हैं, तो सभी चैनलों में विचलन के कुछ वर्गों के लिए लागत फ़ंक्शन को बदल दें, और लंबे समय तक प्रतीक्षा करें (लूप से बाहर जाने के लिए पुनरावृत्तियों की संख्या में वृद्धि) 200), केवल 100 अंकों के साथ कुछ अच्छी छवियां बनाना संभव है:

छवि 11, 100 अंक चित्र 2, 100 अंक छवि 4, 100 अंक चित्र 10, 100 अंक


3

बिंदु-भार मानचित्र के रूप में छवि ऊर्जा का उपयोग करना

इस चुनौती के लिए मेरे दृष्टिकोण में, मैं एक विशेष छवि क्षेत्र की "प्रासंगिकता" को संभावना के लिए मैप करने का एक तरीका चाहता था कि एक विशेष बिंदु को वोरोनोई सेंट्रोइड के रूप में चुना जाएगा। हालाँकि, मैं अभी भी बेतरतीब ढंग से छवि बिंदुओं को चुनकर वोरोनोई मोसिंगिंग की कलात्मक भावना को संरक्षित करना चाहता था। इसके अतिरिक्त, मैं बड़ी छवियों पर काम करना चाहता था, इसलिए मैं डाउनसम्पलिंग प्रक्रिया में कुछ भी नहीं खोता। मेरा एल्गोरिथ्म लगभग इस तरह है:

  1. प्रत्येक छवि के लिए, एक तीक्ष्णता मानचित्र बनाएँ। तीक्ष्णता का नक्शा सामान्यीकृत छवि ऊर्जा (या छवि के उच्च आवृत्ति संकेत के वर्ग) द्वारा परिभाषित किया गया है। एक उदाहरण इस तरह दिखता है:

तीखेपन का नक्शा

  1. छवि से कई बिंदु उत्पन्न करते हैं, तीखेपन के नक्शे में बिंदुओं से 70 प्रतिशत और अन्य सभी बिंदुओं से 30 प्रतिशत लेते हैं। इसका मतलब है कि छवि के उच्च-विस्तार वाले हिस्सों से अधिक सघनता से अंक का नमूना लिया जाता है।
  2. रंग!

परिणाम

N = 100, 500, 1000, 3000

छवि 1, एन = 100 छवि 1, एन = 500 छवि 1, एन = 1000 छवि 1, एन = 3000

छवि 2, एन = 100 छवि 2, एन = 500 छवि 2, एन = 1000 छवि 2, एन = 3000

छवि 3, एन = 100 छवि 3, एन = 500 छवि 3, एन = 1000 छवि 3, एन = 3000

छवि 4, एन = 100 छवि 4, एन = 500 छवि 4, एन = 1000 छवि 4, एन = 3000

छवि 5, एन = 100 छवि 5, एन = 500 छवि 5, एन = 1000 छवि 5, एन = 3000

छवि 6, एन = 100 छवि 6, एन = 500 छवि 6, एन = 1000 छवि 6, एन = 3000

छवि 7, एन = 100 छवि 7, एन = 500 छवि 7, एन = 1000 छवि 7, एन = 3000

छवि 8, एन = 100 छवि 8, एन = 500 छवि 8, एन = 1000 छवि 8, एन = 3000

छवि 9, एन = 100 छवि 9, एन = 500 छवि 9, एन = 1000 छवि 9, एन = 3000

छवि 10, एन = 100 छवि 10, एन = 500 छवि 10, एन = 1000 छवि 10, एन = 3000

छवि 11, एन = 100 छवि 11, एन = 500 छवि 11, एन = 1000 छवि 11, एन = 3000

छवि 12, एन = 100 छवि 12, एन = 500 छवि 12, एन = 1000 छवि 12, एन = 3000

छवि 13, एन = 100 छवि 13, एन = 500 छवि 13, एन = 1000 छवि 13, एन = 3000

छवि 14, एन = 100 छवि 14, एन = 500 छवि 14, एन = 1000 छवि 14, एन = 3000


14
क्या आप इसको उत्पन्न करने के लिए उपयोग किए जाने वाले स्रोत कोड सहित) और b) प्रत्येक थंबनेल को पूर्ण-आकार की छवि से जोड़ेंगे?
मार्टिन एंडर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.