问题
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:
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.
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:
But if we look closer there are a lot of pixels on the left side:
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();
}
回答1:
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;
}
}
}
回答2:
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]]
回答3:
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.
来源:https://stackoverflow.com/questions/8327776/set-pixel-on-randomized-position-depending-on-brightness-in-the-underlying-image