случайным образом рисуйте пиксели на экране с неравным распределением вероятностей

#delphi #random #probability

Вопрос:

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

Пример: Допустим, экран имеет разрешение 1920 x 1080 пикселей. В случае розыгрыша вероятность того, что будут нарисованы пиксели, расположенные в прямоугольнике размером 100 х 100 в позиции (500 500), должна быть в 10 раз выше, чем для пикселей за пределами прямоугольника.

Для достижения этой цели я сначала создаю массив, содержащий вероятность. Позиции внутри прямоугольника получают значение 10, все остальные позиции получают значение 1.

  for i := 1 to 1920 do
 begin
   for j := 1 to 1080 do
   begin
     FProbability[i, j]:=1;
     if InRange(i, 500, 600) and InRange(j, 500, 600) then
     begin
       FProbability[i, j]:=10;
     end;
   end;
 end;
 

Затем я составляю список всех пикселей:

  FPixelList:=TList<TPoint>.Create;
 for i := 1 to 1920 do
 begin
   for j := 1 to 1080 do
   begin
     for k := 1 to FProbabilty[i, j] do
     begin
       FPixelList.Add(TPoint.Create(i,j))
     end;
   end;
 end;
 

Список пикселей теперь содержит 10 записей для каждого пикселя внутри прямоугольника и 1 запись для всех остальных позиций пикселей.

В случае розыгрыша я получаю положение пикселя, которое будет нарисовано

 FPixelList[RandomRange(0, FPixelList.Count-1)]
 

Это прекрасно работает.

Однако мне было интересно, есть ли другие решения этой проблемы. Мое решение использует много памяти, если размеры экрана становятся больше, и я могу использовать только целочисленные значения для вероятности.

Комментарии:

1. Обратите внимание, что это, по сути, вопрос, не зависящий от языка; ответ будет (по сути) одинаковым в сборке C , C#, Rust или x86. На самом деле, это едва ли вопрос программирования: по сути, это вопрос чистой математики.

2. Для этого вам не нужна никакая память. Предположим, проблема в том, что вам нужны орел и решка, но вероятность решки в два раза выше. Затем используйте Случайное(3), и 0 означает орел, 1 или 2 означает решку. Расширьте эту концепцию, и вы закончите.

Ответ №1:

Как отметил Дэвид Хеффернан в комментариях, для этого нет необходимости использовать дополнительное хранилище.

Вместо этого мы можем расширить диапазон случайного выбора за пределы размера изображения, чтобы включить (regionOptions-1)*regionSize дополнительные значения, где regionOptions представлена повышенная вероятность выбора пикселя, в вашем примере это было 10, в области размера regionSize . Если случайно выбранное значение меньше размера изображения, то мы используем его непосредственно в качестве позиции на изображении. Если он больше, то мы выполняем расчет, чтобы определить соответствующую позицию в интересующей области.

Боюсь, я не знаком с Delphi, поэтому для иллюстрации приведу несколько примеров Java-кода.

 int imWidth = 1920;
int imHeight = 1000;
int[][] image = new int[imWidth][imHeight];

int regionOptions = 10;
Rectangle region = new Rectangle(500, 500, 100, 100);

int imSize = imWidth * imHeight;

int randomRange = imSize   (regionOptions-1)*region.width*region.height;
        
int max = 0;
Random rand = new Random();
for(int i=0; i<region.width*region.height*1000; i  )
{
    int randPos = rand.nextInt(randomRange);
    
    int x, y;
    if(randPos < imSize)
    {
        x = randPos % imWidth;
        y = randPos / imWidth;
    }
    else
    {
        randPos = (randPos - imSize) / (regionOptions - 1);
        x = region.x   randPos % region.width;
        y = region.x   randPos / region.height;             
    }

    image[x][y]  = 1;
    max = Math.max(max, image[x][y]);
}
 

И я использовал следующий код для создания нормализованного изображения с целью демонстрации:

 BufferedImage im = new BufferedImage(imWidth, imHeight, BufferedImage.TYPE_BYTE_GRAY);
for(int i=0; i<imWidth; i  )
{
    for(int j=0; j<imHeight; j  )
    {
        int level = (int)(255.0*image[i][j]/max);
        im.setRGB(i, j, (level << 16) | (level << 8) | level);
    }
}       
try
{
    ImageIO.write(im, "png", new File("probIm.png"));
}
catch(Exception e)
{
    e.printStackTrace();
}
 

Это вырезанная область из изображения с использованием regionOptions = 2 . При значении 10 нормализованные пиксели слишком сгруппированы в интересующей области, чтобы отображать какой-либо фон.

введите описание изображения здесь

Ответ №2:

Почему бы не использовать другой подход: сначала нарисуйте определенное количество случайно расположенных пикселей по всей области, подсчитав, сколько из них было нарисовано в сфокусированной области и сколько в нормальной области.

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

Код для этого будет выглядеть примерно так:

 procedure TForm2.Button1Click(Sender: TObject);
var X,Y,N: Integer;
    Rect: Trect;
    Pt: Tpoint;
    NormalAreaCount, FocusedAreaCount: Integer;
begin
  //Set the rectagle boundaries where pixels needs to be more focused
  Rect.Left := 500;
  Rect.Top := 500;
  Rect.Width := 100;
  Rect.Height := 100;

  //Initialize pixels count variables to 0. Othervise they might end up bein unitialized.
  NormalAreaCount := 0;
  FocusedAreaCount := 0;

  //Use first loop do draw certain number of random pixels across the whole area
  for N := 0 to 200 do
  begin
    X := Random(PaintBox1.Width);
    Y := Random(PaintBox1.Height);
    PaintBox1.Canvas.Pixels[X,Y] := clBlack;
    Pt.X := X;
    Pt.Y := Y;
    if PtInRect(Rect,Pt) then
    begin
      //if the drawn pixel was in are of focus rectangle increase FocusedAreaCount
      Inc(FocusedAreaCount);
    end
    //else increase NormalAreaCount
    else Inc(NormalAreaCount);
  end;

  //In scond loop draw more random pixels only in focused rectangle area
  //You get the number of pixels taht needs to be drawn by multiplying the number of pixels
  //taht were drawn outsite focused area by 10 and then substract the number of pixels that
  //were already drawn within the focused area
  for N := 0 to (NormalAreaCount*10)-FocusedAreaCount-1 do
  begin
    X := RandomRange(Rect.Left,Rect.Right);
    Y := RandomRange(Rect.Top,Rect.Bottom);
    PaintBox1.Canvas.Pixels[X,Y] := clBlack;
    //Here I increase the FocusedArea count so I can verify the corect number of pixels drawn
    Inc(FocusedAreaCount);
  end;

  //Simply display the number of pixels drawn in focused and normal area for visual verificatiuon
  Form2.Caption := IntToStr(NormalAreaCount) ':' IntToStr(FocusedAreaCount);
end;
 

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