2

I hope this is an interesting question for some people.

I want to create some pixels on randomized positions on an image, but the randomizing should be depended on the brightness, so the possibility to create a pixel should be high on a bright part of the image and low on a dark part (but still possible).

Lets take this image for example:

Clouds

I want to create a function SetRandomizedPixel which will get the bitmap and sets a pixel on a randomized position. The possibility that the pixel will be created on position 1 should be high, on position 2 medium and on position 3 low.

Clouds with numbers

I'm working with C# and Bitmap/Graphic, but that should not matter.

The only thing I need is a suggestion how to start because I can't figure out a good way to archive this.

Maybe read the brightness and positions of all image pixels and sort the list by brightness - but how can I do a randomize which prefer the brighter areas then?


UPDATE: Working on the answer

The following code results in images like this:

Randomized image

But if we look closer there are a lot of pixels on the left side:

Randomized image zoomed

That's the code (C#/MVC3):

public ActionResult RandomTest()
{

    const int points = 500;

    Bitmap bitmap = new Bitmap(Server.MapPath("~/Files/random-test.jpg"));
    Random random = new Random();

    int imageWidth = bitmap.Width;
    int imageHeight = bitmap.Height;

    float[][] weightedPixels = ConvertImageToGrayScale(bitmap);
    float totalValue = weightedPixels.Sum(i => i.Sum());

    for (var y = 0; y < imageHeight - 1; y++)
    {
        for (var x = 0; x < imageWidth - 1; x++)
        {
            weightedPixels[y][x] /= totalValue;
        }
    }

    for (var i = 0; i < points; i++)
    {
        double randomNumber = random.NextDouble();
        double currentSum = 0;
        for (var y = 0; y < imageHeight - 1; y++)
        {
            for (var x = 0; x < imageWidth - 1; x++)                    
            {
                currentSum += weightedPixels[y][x];
                if (currentSum >= randomNumber)
                {
                    bitmap.SetPixel(x, y, Color.Red);
                    break; 
                }
            }
        }
    }

    // output
    var stream = new MemoryStream();
    bitmap.Save(stream, ImageFormat.Png);
    return File(stream.ToArray(), "image/png");
}


public float[][] ConvertImageToGrayScale(Bitmap bm)
{
    var b = new Bitmap(bm);
    var data = new List<float[]>();
    for (var i = 0; i < b.Width; i++)
    {
        var row = new List<float>();
        for (int x = 0; x < b.Height; x++)
        {
            var oc = b.GetPixel(i, x);
            var grayScale = (int)((oc.R * 0.3) + (oc.G * 0.59) + (oc.B * 0.11));
            row.Add(grayScale);
        }
        data.Add(row.ToArray());
    }
    return data.ToArray();
}
Marc
  • 6,749
  • 9
  • 47
  • 78

3 Answers3

4

You could take a grey scale image of the picture and then sum up the total value of the pixels. Then if you divide each pixel by this total you get a weighting. Then you pick a number between 0 and 1 and start at the first weighted pixel and keep adding up the wieghts until you get to the first pixel who makes the sum greater than your random number. Then color that pixel on the original image however you want.

Edit: Psuedo Code

int imageWidth = ...
int imageHeight = ...
int[][] grayScale = ConvertImageToGrayScale(yourImage);
int totalValue = grayScale.Sum();

float[][] weightedPixels = grayScale.AsType(float[][]);
for (int y in 0...imageHeight-1)
     for (int x in 0...imageWidth-1)
         weightedPixels[y][x] /= totalValue;

float randomNumber = RandomReal();
float currentSum = 0;
for (int y in 0...imageHeight-1)
     for (int x in 0...imageWidth-1)
         currentSum += weightedPixels[y][x];
         if (currentSum >= randomNumber)
             break 2; // Here is your random pixel at (x, y)

Edit: Theory

The theory behind this is that we want to transform the image to a probability distribution based on the brightness of each pixel. This way brighter pixels are more likely to be picked than dimmer ones. The conversion to grayscale aids us in generating the PDF or weightedMatrix by reducing each pixel to a single number instead of an RGB triple. We take the sume of the grayscale matrix to get the total value which is needed to create the weighted matrix. The weighted matrix is intialized with the grayscale matrix, but then each element is divided by the total weight. This means that the sum of the weighted matrix is 1, which is a requirement for it to represent a PDF. Now we can pick a random probability, which is between 0 and 1. Using this we pick a pixel by summing along the weighted matrix until the sum is just greater than our probability. The pixel at which that happens is our randomly picked pixel. This is a standard way of randomly picking an item from a list with probabilities associated with each item.

Edit: Fixing Left-Side Line

The problem the OP brought up has to with boundry conditions from looping over the array in Y,X scan lines. If the code is reworked to use a linear array and then transform the index into an X,Y pair the solid line on the left is removed. If the middle chunk of the main method is changed to the following it works as intended:

float[] weightedPixels = ConvertImageToGrayScale(bitmap).SelectMany(r => r).ToArray();
float totalValue = weightedPixels.Sum();

for ( int i = 0; i < weightedPixels.Length; i++) {
        weightedPixels[i] /= totalValue;
}

for (int pIdx = 0; pIdx < points; pIdx++)
{
    double randomNumber = random.NextDouble();
    double currentSum = 0;
    for (int i = 0; i < weightedPixels.Length; i++)
    {
        currentSum += weightedPixels[i];
        if (currentSum >= randomNumber)
        {
            int y = i / imageWidth;
            int x = i % imageWidth;
            bitmap.SetPixel(x, y, Color.Red);
            break;
        }
    }
}
troutinator
  • 1,160
  • 1
  • 7
  • 22
  • +1, but you should handle the obvious division by zero for a completely black image somehow – Niki Nov 30 '11 at 15:36
  • True. Though if in that case the real intent is to still have some pixels chosen then perhaps the best bet is to actually add a small offset to the grayscale matrix to ensure there are no zero elements. That way every pixel stands a chance of being picked. It would be a minor adjustment to the code. – troutinator Nov 30 '11 at 15:42
  • Thank you very much, I built your code in C# and it works fine but a little problem is still there. Could you have a look at my updated answer? – Marc Dec 01 '11 at 11:39
  • I added the fix to my answer. Hope that solves your problem. I'd have to give it more thought as to exactly why it was happening. – troutinator Dec 02 '11 at 16:04
  • Perfect, thanks mate! Didn't notice your answer until now, sorry for the delay. – Marc Dec 07 '11 at 02:52
1

You are looking for weighted random picks.

The answer to this question should get you started on the low-level programming: Weighted random numbers

This is how it looks in Mathematica, using the built-in function RandomChoice:

{w, h} = ImageDimensions@img;
brightness = ImageData@ColorConvert[img, "Grayscale"];

(* number of pixels to change *)
npix = 150;
(* new value for changed pixels *) 
newrgb = {1, 1, 1};   

Image[ ReplacePart[
    ImageData@img, 
    RandomChoice[
        Flatten@brightness -> Tuples[{Range[1, h], Range[1, w]}],
        npix] -> newrgb]]

enter image description here

Community
  • 1
  • 1
Matthias Odisio
  • 2,038
  • 12
  • 19
  • Thanks a lot, I'm sure your code will work but the answer of troutinator was more easily for me to get it into my code. Do you mean [this](http://en.wikipedia.org/wiki/Mathematica) with Mathematica? – Marc Dec 01 '11 at 11:43
  • Yes, this is the software I used in the example. – Matthias Odisio Dec 01 '11 at 12:12
1

I like the weighted random numbers solutions of both troutinator and Matthias Odisio, but I think that for a large image this might be CPU intensive. Especially if several pixels have to be picked up. I would prefer a Monte Carlo simulation approach. Here is some pseudo-code:

Normalize(I)                //in [0,1] range, with 1=bright
n=0
While n < number_of_pixels_to_pick
  x = random(image_size(1)) //random pixel position
  y = random(image_size(2))
  p = random()              // in [0,1] range
  If p < I(x,y) do
    select pixel
    n=n+1
  Endif
Endwhile

Edit: Against this solution, if the image is very dark, it may loop for longer than the deterministic approach.

Hugues Fontenelle
  • 5,275
  • 2
  • 29
  • 44
  • Thanks and you are right, the code of troutinator *is* CPU intensive on a large image, but that should be no problem for me. But what do you mean with Normalize(I) in your example? – Marc Dec 01 '11 at 11:46
  • By Normalize, I mean setting the brightest pixel to 1 and the darkest pixel to 0. The formula is Normalize(I)=(I-min(I))/(max(I)-min(I)) – Hugues Fontenelle Dec 01 '11 at 14:07