Use Webcam as ambient light sensor in PC - c#

Is it possible to make the webcam of a pc acting as an ambient light sensor?
I am using .Net 4.5 framework in Windows 8 pro.

Philippe's answer to this question: How to calculate the average rgb color values of a bitmap has code that will calculate the average RGB values of a bitmat image (bm in his code):
BitmapData srcData = bm.LockBits(
new Rectangle(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
int stride = srcData.Stride;
IntPtr Scan0 = dstData.Scan0;
long[] totals = new long[] {0,0,0};
int width = bm.Width;
int height = bm.Height;
unsafe
{
byte* p = (byte*) (void*) Scan0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
for (int color = 0; color < 3; color++)
{
int idx = (y*stride) + x*4 + color;
totals[color] += p[idx];
}
}
}
}
int avgR = totals[0] / (width*height);
int avgG = totals[1] / (width*height);
int avgB = totals[2] / (width*height);
Once you have the average RGB values you can use Color.GetBrightness to determine how light or dark it is. GetBrightness will return a number between 0 and 1, 0 is black, 1 is white. You can use something like this:
Color imageColor = Color.FromARGB(avgR, avgG, avgB);
double brightness = imageColor.GetBrightness();
You could also convert the RGB values to HSL and look at the "L", that may be more accurate, I don't know.

Related

How to get color from specific area in an Image in C#

I am trying to get color from specific area in an Image.
Assume that , this is image , and I want to get color inside image.(the result should be red of the above image) This color may be different position in image. Because I don't know exact position of color where it starting, so I can't get exact result.
Until now, I cropped image giving manually position of x and y, and then cropped image and I got average color of cropped image. But I know , this is not exact color.
What I tried :
private RgbDto GetRGBvalueCroppedImage(Image croppedImage)
{
var avgRgb = new RgbDto();
var bm = new Bitmap(croppedImage);
BitmapData srcData = bm.LockBits(
new Rectangle(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
int stride = srcData.Stride;
IntPtr Scan0 = srcData.Scan0;
long[] totals = new long[] { 0, 0, 0 };
int width = bm.Width;
int height = bm.Height;
unsafe
{
byte* p = (byte*)(void*)Scan0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
for (int color = 0; color < 3; color++)
{
int idx = (y * stride) + x * 4 + color;
totals[color] += p[idx];
}
}
}
}
avgRgb.avgB = (int)totals[0] / (width * height);
avgRgb.avgG = (int)totals[1] / (width * height);
avgRgb.avgR = (int)totals[2] / (width * height);
return avgRgb;
}
How can I get exact position to crop? May be I can convert image to byte array, then I can find different color and take position of it and then crop. But I have no clue how do this.
You can use something this extension method to get dominant color in a region of an image in case they are not all the same
public static Color GetDominantColor(this Bitmap bitmap, int startX, int startY, int width, int height) {
var maxWidth = bitmap.Width;
var maxHeight = bitmap.Height;
//TODO: validate the region being requested
//Used for tally
int r = 0;
int g = 0;
int b = 0;
int totalPixels = 0;
for (int x = startX; x < (startX + width); x++) {
for (int y = startY; y < (startY + height); y++) {
Color c = bitmap.GetPixel(x, y);
r += Convert.ToInt32(c.R);
g += Convert.ToInt32(c.G);
b += Convert.ToInt32(c.B);
totalPixels++;
}
}
r /= totalPixels;
g /= totalPixels;
b /= totalPixels;
Color color = Color.FromArgb(255, (byte)r, (byte)g, (byte)b);
return color;
}
You can then use it like
Color pixelColor = myBitmap.GetDominantColor(xPixel, yPixel, 5, 5);
there is room for improvement, like using a Point and Size, or even a Rectangle
public static Color GetDominantColor(this Bitmap bitmap, Rectangle area) {
return bitmap.GetDominantColor(area.X, area.Y, area.Width, area.Height);
}
and following this link:
https://www.c-sharpcorner.com/UploadFile/0f68f2/color-detecting-in-an-image-in-C-Sharp/
If you want to get the image colors, you don't need to do any cropping at all. Just loop on image pixels and find the two different colors. (Assuming that you already know the image will have exactly 2 colors, as you said in comments). I've written a small function that will do that. However, I didn't test it in an IDE, so expect some small mistakes:
private static Color[] GetColors(Image image)
{
var bmp = new Bitmap(image);
var colors = new Color[2];
colors[0] = bmp.GetPixel(0, 0);
for (int i = 0; i < bmp.Width; i++)
{
for (int j = 0; j < bmp.Height; j++)
{
Color c = bmp.GetPixel(i, j);
if (c == colors[0]) continue;
colors[1] = c;
return colors;
}
}
return colors;
}

C# RGB[,] to picturebox

I have a data that is (2448*2048) 5Mpixel image data, but the picturebox only has (816*683) about 500,000 pixels, so I lowered the pixels and I only need a black and white image, so I used the G value to create the image, but The image I output is shown in the following figure. Which part of my mistake?
public int[,] lowered(int[,] greenar)
{
int[,] Sy = new int[816, 683];
int x = 0;
int y = 0;
for (int i = 1; i < 2448; i += 3)
{
for (int j = 1; j < 2048; j += 3)
{
Sy[x, y] = greenar[i, j];
y++;
}
y = 0;
x++;
}
return Sy;
}
static Bitmap Create(int[,] R, int[,] G, int[,] B)
{
int iWidth = G.GetLength(1);
int iHeight = G.GetLength(0);
Bitmap Result = new Bitmap(iWidth, iHeight,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Rectangle rect = new Rectangle(0, 0, iWidth, iHeight);
System.Drawing.Imaging.BitmapData bmpData = Result.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
IntPtr iPtr = bmpData.Scan0;
int iStride = bmpData.Stride;
int iBytes = iWidth * iHeight * 3;
byte[] PixelValues = new byte[iBytes];
int iPoint = 0;
for (int i = 0; i < iHeight; i++)
{
for (int j = 0; j < iWidth; j++)
{
int iG = G[i, j];
int iB = G[i, j];
int iR = G[i, j];
PixelValues[iPoint] = Convert.ToByte(iB);
PixelValues[iPoint + 1] = Convert.ToByte(iG);
PixelValues[iPoint + 2] = Convert.ToByte(iR);
iPoint += 3;
}
}
System.Runtime.InteropServices.Marshal.Copy(PixelValues, 0, iPtr, iBytes);
Result.UnlockBits(bmpData);
return Result;
}
https://upload.cc/i1/2018/04/26/WHOXTJ.png
You don't need to downsample your image, you can do it in this way. Set picturebox property BackgroundImageLayout as either zoom or stretch and assign it as:
picturebox.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
picturebox.BackgroundImage = bitmap;
System.Windows.Forms.ImageLayout.Zoom will automatically adjust your bitmap to the size of picturebox.
You seem to be constantly mixing up your x and y offsets, which can easily be avoided simply by actually calling your loop variables x and y whenever you loop through image data. Also, image data is generally saved line by line, so your outer loop should be the Y loop going over the height, and the inner loop should process the X coordinates on one line, and should thus loop over the width.
Also, I'm not sure where your original data comes from, but in most of the cases I've seen where the image data is in multidimensional arrays like this, the Y is actually the first index in the array. Your actual image building function also assumes this, since it uses G.GetLength(0) to get the height of the image. But your channel resize function doesn't; it makes a multidimensional array as new int[816, 683], which would be a 683*816 image, not 816*683 as you said. So that certainly seems wrong.
Since you confirmed it to be [x,y], I adapted this solution to use it like that.
That aside, you hardcoded a lot of values in your functions, which is very bad practice. If you know you will reduce the image to 1/3rd by taking only one in three pixels, just give that 3 as parameter.
The reduction code:
public static Int32[,] ResizeChannel(Int32[,] origChannel, Int32 lossfactor)
{
Int32 newWidth = origChannel.GetLength(0) / lossfactor;
Int32 newHeight = origChannel.GetLength(1) / lossfactor;
// to avoid rounding errors
Int32 origHeight = newHeight * lossfactor;
Int32 origWidth = newWidth *lossfactor;
Int32[,] newChannel = new Int32[newWidth, newHeight];
Int32 newX = 0;
Int32 newY = 0;
for (Int32 y = 1; y < origHeight; y += lossfactor)
{
newX = 0;
for (Int32 x = 1; x < origWidth; x += lossfactor)
{
newChannel[newX, newY] = origChannel[x, y];
newX++;
}
newY++;
}
return newChannel;
}
The actual build code, as was remarked by GSerg in the comments, is wrong because you don't take the stride into account. The stride is the actual byte length of each line of pixels, and this is not just width * BytesPerPixel, since it gets rounded up to the next multiple of 4 bytes.
So you need to initialize your array as height * stride, not as height * width * 3, and you need to skip your write offset to the next multiple of the stride whenever you go to a lower Y line, rather than assuming it will just get there automatically because your X processing adds 3 for each pixel. Because it will not get there automatically, unless, by pure coincidence, your image width happens to be a multiple of 4 pixels.
Also, if you only use one channel for this, there is no reason to give it all three channels. Just give a single one.
public static Bitmap CreateGreyImage(Int32[,] greyChannel)
{
Int32 width = greyChannel.GetLength(0);
Int32 height = greyChannel.GetLength(1);
Bitmap result = new Bitmap(width, height, PixelFormat.Format24bppRgb);
Rectangle rect = new Rectangle(0, 0, width, height);
BitmapData bmpData = result.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
Int32 stride = bmpData.Stride;
// stride is the actual line width in bytes.
Int32 bytes = stride * height;
Byte[] pixelValues = new Byte[bytes];
Int32 offset = 0;
for (Int32 y = 0; y < height; y++)
{
Int32 workOffset = offset;
for (Int32 x = 0; x < width; x++)
{
pixelValues[workOffset + 0] = (Byte)greyChannel[x, y];
pixelValues[workOffset + 1] = (Byte)greyChannel[x, y];
pixelValues[workOffset + 2] = (Byte)greyChannel[x, y];
workOffset += 3;
}
// Add stride to get the start offset of the next line
offset += stride;
}
Marshal.Copy(pixelValues, 0, bmpData.Scan0, bytes);
result.UnlockBits(bmpData);
return result;
}
Now, this works as expected if your R, G and B channels are indeed identical, But if they are not, you have to realize there is a difference between reducing the image to grayscale and just building a grey image from the green channel. On a colour image, you will get totally different results if you take the blue or red channel instead.
This was the code I executed for this:
Int32[,] greyar = ResizeChannel(greenar, 3);
Bitmap newbm = CreateGreyImage(greyar);

Performing multiple image averaging in c#

I wish to calculate an average image from 3 different images of the same size that I have. I know this can be done with ease in matlab..but how do I go about this in c#? Also is there an Aforge.net tool I can use directly for this purpose?
I have found an article on SO which might point you in the right direction. Here is the code (unsafe)
BitmapData srcData = bm.LockBits(
new Rectangle(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
int stride = srcData.Stride;
IntPtr Scan0 = srcData.Scan0;
long[] totals = new long[] {0,0,0};
int width = bm.Width;
int height = bm.Height;
unsafe
{
byte* p = (byte*) (void*) Scan0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
for (int color = 0; color < 3; color++)
{
int idx = (y*stride) + x*4 + color;
totals[color] += p[idx];
}
}
}
}
int avgB = totals[0] / (width*height);
int avgG = totals[1] / (width*height);
int avgR = totals[2] / (width*height);
Here is the link to the article:
How to calculate the average rgb color values of a bitmap
ImageMagick can do this for you very simply - at the command-line you could type this:
convert *.bmp -evaluate-sequence mean output.jpg
You could equally calculate the median (instead of the mean) of a bunch of TIFF files and save in a PNG output file like this:
convert *.tif -evaluate-sequence median output.png
Easy, eh?
There are bindings for C/C++, Perl, PHP and all sorts of other janguage interfaces, including (as kindly pointed out by #dlemstra) the Magick.Net binding available here.
ImageMagick is powerful, free and available here.

desaturate an OpenGL texture

I have a .png with transparency that I need to desaturate. I read I need to take the average R,G and B value of the bitmap then use:
G*.59
R*.3
B*.11
I calculate the average color in this way:
private Color Average_Color(Bitmap bitmap) {
Color c = new Color();
int pixel_number = 0;
int r = 0;
int g = 0;
int b = 0;
for (int i = 0; i < bitmap.Width; i++) {
for (int j = 0; j < bitmap.Height; j++) {
c = bitmap.GetPixel(i, j);
r += c.R;
g += c.G;
b += c.B;
pixel_number++;
}
}
c = Color.FromArgb(1, r / pixel_number, g / pixel_number, b / pixel_number);
return c;
}
Then, first to paint my texture, I set the Color in this way:
rgb = Average(bitmap);
GL.Color3(rgb.R * 0.59, rgb.G * 0.3, rgb.B * 0.11);
//here I draw my texture
I don't know why but it doesn't work (I get the texture with his original colors). I guess it's something wrong in Average_Color. Maybe because it's not a total opaque bitmap?
OpenGL expects its colors to be floats normalized from 0 to 1, but I suspect that your bitmap is reporting colors from 0-255.
Therefore I assume you're passing RGB values much greater than 1 to glColor, which get clamped to 1, so your texture looks the same.
Try
GL.Color3(rgb.R*0.59/255.f, rgb.G*0.3/255.f, rgb.B*0.11/255.f);
Try casting your variables to floats before the divide

unsafe image noise removal in c# (error : Bitmap region is already locked)

public unsafe Bitmap MedianFilter(Bitmap Img)
{
int Size =2;
List<byte> R = new List<byte>();
List<byte> G = new List<byte>();
List<byte> B = new List<byte>();
int ApetureMin = -(Size / 2);
int ApetureMax = (Size / 2);
BitmapData imageData = Img.LockBits(new Rectangle(0, 0, Img.Width, Img.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
byte* start = (byte*)imageData.Scan0.ToPointer ();
for (int x = 0; x < imageData.Width; x++)
{
for (int y = 0; y < imageData.Height; y++)
{
for (int x1 = ApetureMin; x1 < ApetureMax; x1++)
{
int valx = x + x1;
if (valx >= 0 && valx < imageData.Width)
{
for (int y1 = ApetureMin; y1 < ApetureMax; y1++)
{
int valy = y + y1;
if (valy >= 0 && valy < imageData.Height)
{
Color tempColor = Img.GetPixel(valx, valy);// error come from here
R.Add(tempColor.R);
G.Add(tempColor.G);
B.Add(tempColor.B);
}
}
}
}
}
}
R.Sort();
G.Sort();
B.Sort();
Img.UnlockBits(imageData);
return Img;
}
I tried to do this. but i got an error call "Bitmap region is already locked" can anyone help how to solve this. (error position is highlighted)
GetPixel is the slooow way to access the image and doesn't work (as you noticed) anymore if someone else starts messing with the image buffer directly. Why would you want to do that?
Check Using the LockBits method to access image data for some good insight into fast image manipulation.
In this case, use something like this instead:
int pixelSize = 4 /* Check below or the site I linked to and make sure this is correct */
byte* color =(byte *)imageData .Scan0+(y*imageData .Stride) + x * pixelSize;
Note that this gives you the first byte for that pixel. Depending on the color format you are looking at (ARGB? RGB? ..) you need to access the following bytes as well. Seems to suite your usecase anyway, since you just care about byte values, not the Color value.
So, after having some spare minutes, this is what I'd came up with (please take your time to understand and check it, I just made sure it compiles):
public void SomeStuff(Bitmap image)
{
var imageWidth = image.Width;
var imageHeight = image.Height;
var imageData = image.LockBits(new Rectangle(0, 0, imageWidth, imageHeight), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
var imageByteCount = imageData.Stride*imageData.Height;
var imageBuffer = new byte[imageByteCount];
Marshal.Copy(imageData.Scan0, imageBuffer, 0, imageByteCount);
for (int x = 0; x < imageWidth; x++)
{
for (int y = 0; y < imageHeight; y++)
{
var pixelColor = GetPixel(imageBuffer, imageData.Stride, x, y);
// Do your stuff
}
}
}
private static Color GetPixel(byte[] imageBuffer, int imageStride, int x, int y)
{
int pixelBase = y*imageStride + x*3;
byte blue = imageBuffer[pixelBase];
byte green = imageBuffer[pixelBase + 1];
byte red = imageBuffer[pixelBase + 2];
return Color.FromArgb(red, green, blue);
}
This
Relies on the PixelFormat you used in your sample (regarding both the pixelsize/bytes per pixel and the order of the values). If you change the PixelFormat this will break.
Doesn't need the unsafe keyword. I doubt that it makes a lot of difference, but you are free to use the pointer based access instead, the method would be the same.

Categories