Случайность Python random

Я использую Python для генерации изображений, используя пунктирные линии для пунктировки. Период штрихов является постоянным, что меняет соотношение штрих/пространство. Это производит что-то вроде этого:

enter image description here

Однако на этом изображении штрих имеет равномерное происхождение, и это создает неприглядные вертикальные желоба. Поэтому я попытался рандомизировать происхождение, чтобы удалить желоба. Это работает, но есть очевидная закономерность:

enter image description here

Интересно, откуда это взялось, я сделал очень простой тестовый пример со сложенными пунктирными прямыми:

  • коэффициент тире: 50%
  • тире 20px
  • смещение начала координат от -10px до +10px с использованием random.uniform(-10.,+10.) (*) (после начального random.seed()

enter image description here

И с добавленной случайностью:

enter image description here

Так что есть еще шаблон. Чего я не понимаю, так это того, что для получения видимого желоба необходимо, чтобы 6 или 7 последовательных значений попадали в один и тот же диапазон (скажем, половина общего диапазона), что должно составлять 1/64 вероятности, но, похоже, происходит много чаще всего в 200 сгенерированных строках.

Я что-то неправильно понимаю? Это просто наш человеческий мозг, который видит шаблоны там, где их нет? Может ли быть лучший способ генерировать что-то более "визуально случайное" (python 2.7 и желательно без установки чего-либо)?

(*) частичные пиксели действительны в этом контексте

Приложение: код, который я использую (это скрипт Gimp):

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

# Python script for Gimp (requires Gimp 2.10)
# Run on a 400x400 image to see something without having to wait too much
# Menu entry is in "Test" submenu of image menubar

import random,traceback
from gimpfu import *

def constant(minShift,maxShift):
    return 0

def triangle(minShift,maxShift):
    return random.triangular(minShift,maxShift)

def uniform(minShift,maxShift):
    return random.uniform(minShift,maxShift)

def gauss(minShift,maxShift):
    return random.gauss((minShift+maxShift)/2,(maxShift-minShift)/2)

variants=[('Constant',constant),('Triangle',triangle),('Uniform',uniform),('Gauss',gauss)]

def generate(image,name,generator):
    random.seed()
    layer=gimp.Layer(image, name, image.width, image.height, RGB_IMAGE,100, LAYER_MODE_NORMAL)
    image.add_layer(layer,0)
    layer.fill(FILL_WHITE)
    path=pdb.gimp_vectors_new(image,name)

    # Generate path, horizontal lines are 2px apart, 
    # Start on left has a random offset, end is on the right edge right edge
    for i in range(1,image.height, 2):
        shift=generator(-10.,10.)
        points=[shift,i]*3+[image.width,i]*3
        pdb.gimp_vectors_stroke_new_from_points(path,0, len(points),points,False)
    pdb.gimp_image_add_vectors(image, path, 0)

    # Stroke the path
    pdb.gimp_context_set_foreground(gimpcolor.RGB(0, 0, 0, 255))
    pdb.gimp_context_set_stroke_method(STROKE_LINE)
    pdb.gimp_context_set_line_cap_style(0)
    pdb.gimp_context_set_line_join_style(0)
    pdb.gimp_context_set_line_miter_limit(0.)
    pdb.gimp_context_set_line_width(2)
    pdb.gimp_context_set_line_dash_pattern(2,[5,5])
    pdb.gimp_drawable_edit_stroke_item(layer,path)

def randomTest(image):
    image.undo_group_start()
    gimp.context_push()

    try:
        for name,generator in variants:
            generate(image,name,generator)
    except Exception as e:
        print e.args[0]
        pdb.gimp_message(e.args[0])
        traceback.print_exc()

    gimp.context_pop()
    image.undo_group_end()
    return;

### Registration
desc="Python random test"

register(
    "randomize-test",desc,'','','','',desc,"*",
    [(PF_IMAGE, "image", "Input image", None),],[],
    randomTest,menu="<Image>/Test",
)

main()

Ответы

Ответ 1

Думайте об этом так: желоб воспринимается, пока он не заблокирован (или почти так). Это происходит только тогда, когда две последовательные линии почти полностью не в фазе (черные сегменты в первой линии лежат чуть выше белых сегментов в следующей). Такие экстремальные ситуации случаются только в одном из каждых 10 рядов, следовательно, видимые желоба, которые, кажется, расширяются примерно на 10 рядов, прежде чем будут заблокированы.

Посмотрите на это по-другому - если вы распечатываете изображение, на самом деле есть длинные белые каналы, по которым вы можете легко нарисовать линию пером. Почему ваш разум не должен их воспринимать?

Чтобы получить лучшую визуальную случайность, найдите способ сделать последовательные линии зависимыми, а не независимыми, таким образом, чтобы поведение почти в противофазе появлялось чаще.

Ответ 2

Есть, по крайней мере, одна очевидная причина, по которой мы видим шаблон на "случайной" картинке: 400x400 пикселей - это те же самые 20x400 пикселей, которые повторяются 20 раз.

enter image description here

Таким образом, каждое видимое движение повторяется 20 раз параллельно, что действительно помогает мозгу анализировать картину.

На самом деле, один и тот же шаблон шириной 10px повторяется 40 раз, чередуя черный и белый:

enter image description here

Вы можете рандомизировать период тире отдельно для каждой строки (например, между 12 и 28):

enter image description here

Вот соответствующий код:

import numpy as np
import random

from matplotlib import pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = [13, 13]

N = 400

def random_pixels(width, height):
    return np.random.rand(height, width) < 0.5

def display(table):
    plt.imshow(table, cmap='Greys', interpolation='none')
    plt.show()

display(random_pixels(N, N))

def stripes(width, height, stripe_width):
    table = np.zeros((height, width))
    cycles = width // (stripe_width * 2) + 1
    pattern = np.concatenate([np.zeros(stripe_width), np.ones(stripe_width)])
    for i in range(height):
        table[i] = np.tile(pattern, cycles)[:width]
    return table

display(stripes(N, N, 10))

def shifted_stripes(width, height, stripe_width):
    table = np.zeros((height, width))
    period = stripe_width * 2
    cycles = width // period + 1
    pattern = np.concatenate([np.zeros(stripe_width), np.ones(stripe_width)])
    for i in range(height):
        table[i] = np.roll(np.tile(pattern, cycles), random.randrange(0, period))[:width]
    return table

display(shifted_stripes(N, N, 10))

def flexible_stripes(width, height, average_width, delta):
    table = np.zeros((height, width))
    for i in range(height):
        stripe_width = random.randint(average_width - delta, average_width + delta)
        period = stripe_width * 2
        cycles = width // period + 1
        pattern = np.concatenate([np.zeros(stripe_width), np.ones(stripe_width)])
        table[i] = np.roll(np.tile(pattern, cycles), random.randrange(0, period))[:width]
    return table

display(flexible_stripes(N, N, 10, 4))

Ответ 3

Это немного нелогично, но при добавлении случайных элементов случайность становится меньше. Если я правильно следую, диапазон каждого элемента составляет 10 - 30 пикселей. Таким образом, общий размер 10 элементов составляет от 100px до 300px, но распределение даже не распространяется на этот диапазон. Крайности очень маловероятны, и в среднем они будут довольно близки к 200px, так что появится фундаментальный шаблон 20px. Ваше случайное распределение должно избежать этого.

РЕДАКТИРОВАТЬ: я вижу, что я немного неправильно понял, и все тире 20px со случайным смещением. Итак, я думаю, что просмотр любого 1 вертикального набора штрихов будет казаться случайным, но этот же случайный набор повторяется по всей странице, давая шаблон.

Ответ 4

Публикация моего окончательного решения в качестве ответа, но, пожалуйста, опишите других.

Джон Колман имеет точку, когда он говорит:

Чтобы получить лучшую визуальную случайность, найдите способ сделать последовательные линии зависимыми, а не независимыми, таким образом, чтобы поведение почти в противофазе появлялось чаще.

Итак, наконец, лучший способ избежать желобов - это избежать случайности и иметь очень фиксированную схему сдвигов, и хорошо работает 4-фазный цикл 0,25%, 75%, 50%:

enter image description here

Хорошо, еще есть небольшой ромбовидный узор, но он гораздо менее заметен, чем узоры, представленные случайными схемами, которые я пробовал.

Ответ 5

Перейдя по ссылке, размещенной в комментариях, можно найти полезную информацию, поскольку я делаю вид, что пишу что-то, связанное с ней,

Что касается несколько реализаций методы случайной() вы можете найти в некоторых библиотеках питона, как NumPy, случайные механические красиво объяснены здесь, и он говорит:

(pseudo-) случайные числа работают, начиная с числа (начального числа), умножая его на большое число, а затем беря по модулю этого произведения. Полученное число затем используется в качестве начального числа для генерации следующего "случайного" числа. Когда вы устанавливаете семя (каждый раз), оно делает то же самое каждый раз, давая вам одинаковые числа.

Вместо этого, как сказано в ссылочной ссылке, понимая Twister Mersenne, вы можете наблюдать случайный паттерн случайным образом, чтобы предсказать следующий элемент в последовательности, зная немного информации. Тем не менее, немного странно материализовать это наблюдение, посмотрев прямо на изображения, которые вы генерируете, прекрасный пример случайности.

Я считаю, что переход на случайную криптоуровню может быть хорошим шансом для вашей цели, возможно, может помочь чтение контента, похожего на эту, о лучших случайных вещах в python.

Это часть всей истории, укажет нам на библиотеку uuid разработанную, чтобы "гарантировать уникальность в пространстве и времени".

import uuid

>> uuid.uuid4()
UUID('e06bea9b-6cf6-46a0-8438-f61497409193')
>> a=uuid.uuid4()
>> a.bytes
b'\xdal\xdc\xc8\xbe\x15I\x06\xba\xfa {\xd3!'\xf7'

Вы можете использовать информацию из uuid4() для создания отступов между слешами.

Ответ 6

Это хороший пример того, почему настоящий ГСЧ стоит сотни или тысячи долларов. Если вы хотите истинную случайность, вам нужно использовать квантовый источник. Есть хорошие процедуры, генерирующие псевдослучайные числа, но это требует больше усилий, чем вызов встроенных функций для их генерации.

Я бегу симуляции Монте-Карло, где случайность является ключевым. У нас есть самописный ГСЧ (не мной), который генерирует тот же набор случайных чисел на основе начального числа семян, с которого мы начинаем. Результат довольно случайный, но набор всегда одинаков, если начальная точка одинакова. Я рекомендую искать алгоритм, кроме встроенных функций.