Okay, I've created two programs. One that uses GetPixels and another that uses LockBits. My GetPixels program is as follows...
The stripe picture referred is a 200x200 jpg
Stopwatch GetTime = new Stopwatch();
Bitmap img = new Bitmap("stripe.jpg");
GetTime.Start();
for (int i = 0; i < img.Width; i++)
{
for (int j = 0; j < img.Height; j++)
{
Color pixel = img.GetPixel(i, j);
output += " " + pixel;
}
}
GetTime.Stop();
Now this one reads out about 20 secs to process this image and output all the pixels. Great, but my LockBits one should, theoretically be faster. My code for the LockBits is...
Bitmap bmp = new Bitmap("stripe.jpg");
Rectangle bmpRec = new Rectangle(0, 0, bmp.Width, bmp.Height); //Creates Rectangle for holding picture
BitmapData bmpData = bmp.LockBits(bmpRec, ImageLockMode.ReadWrite, Pixels); //Gets the Bitmap data
IntPtr Pointer = bmpData.Scan0; //Scans the first line of data
int DataBytes = Math.Abs(bmpData.Stride) * bmp.Height; //Gets array size
byte[] rgbValues = new byte[DataBytes]; //Creates array
string Pix = " ";
Marshal.Copy(Pointer, rgbValues, 0, DataBytes); //Copies of out memory
bmp.UnlockBits(bmpData);
Stopwatch Timer = new Stopwatch();
pictureBox1.Image = bmp;
Timer.Start();
for (int p = 0; p < DataBytes; p++)
{
Pix += " " + rgbValues[p];
}
Timer.Stop();
and the time on that is 37secs. Now I dont understand why my time is longer for the Lockbits than it is for the GetPixels.
Also my output files don't match up in terms of where they are listed. It is almost as if they are out of order.
This is a big problem to tackle so thank you all in advance for reading and trying to solve my problem.
You have a few problems that I can see. The biggest issue is that your image has a width of 200, but in memory, its stride is 600 (for me - probably similar for you). This means you are writing out a lot more data, because you don't ignore the 400 padding pixels per row.
Other issues:
You're timing string concatenation only. By the time you start your timer, the lockbits stuff has finished.
Your string concatenation would be faster using StringBuilder.
You are locking the bitmap for read/write access, when you only need read. No discernible effect on performance for me, with this image, but still might as well change it to ReadOnly.
Most of your comments are unnecessary at best (// creates array)- some are misleading (//Scans the first line of data - no, it returns a pointer to the data which has already been loaded).
The following code completes in only a few milliseconds on my machine.
Bitmap bmp = new Bitmap(#"d:\stripe.jpg");
//pictureBox1.Image = bmp;
Stopwatch Timer = new Stopwatch();
Rectangle bmpRec = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(
bmpRec, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
IntPtr Pointer = bmpData.Scan0;
int DataBytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[DataBytes];
Marshal.Copy(Pointer, rgbValues, 0, DataBytes);
bmp.UnlockBits(bmpData);
StringBuilder pix = new StringBuilder(" ");
Timer.Start();
for (int i = 0; i < bmpData.Width; i++)
{
for (int j = 0; j < bmpData.Height; j++)
{
// compute the proper offset into the array for these co-ords
var pixel = rgbValues[i + j*Math.Abs(bmpData.Stride)];
pix.Append(" ");
pix.Append(pixel);
}
}
Timer.Stop();
Console.WriteLine(Timer.Elapsed);
Related
I've 2 images that I need to compare of 1280x800 each and I'm too worried about the efficiency as I'll have to do the same operations that include looping each second
I can think of so many ways to loop through a Bitmap object pixels but I don't know which would be more efficient, for now I am using simple for loop but it uses too much memory and processing which I could not afford at this point
Some tweaks here and there and all it does is less memory for more processing or the other way around
Any tips, information or experience is highly appreciated, I'm also open to use external libraries if they have much better efficiency.
from https://stackoverflow.com/a/6094092/1856345
Bitmap bmp = new Bitmap("SomeImage");
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];
byte[] r = new byte[bytes / 3];
byte[] g = new byte[bytes / 3];
byte[] b = new byte[bytes / 3];
// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, bytes);
int count = 0;
int stride = bmpData.Stride;
for (int column = 0; column < bmpData.Height; column++)
{
for (int row = 0; row < bmpData.Width; row++)
{
b[count] = rgbValues[(column * stride) + (row * 3)];
g[count] = rgbValues[(column * stride) + (row * 3) + 1];
r[count++] = rgbValues[(column * stride) + (row * 3) + 2];
}
}
hope you all doing well. I did write a bit of codes in C# using Aforge library. I wanted to crop my main image captured from webcam so as to have a nice ROI. When I use threshold value of 0 everything should be in white pixels (total of lets say 26880 pixels) but it seems that I have some black pixels (578 pixels) within my cropped image. any idea of what may caused it? when I don't crop my image everything is fine.
Bitmap img = (Bitmap)eventArgs.Frame.Clone();
Bitmap bmp = new Bitmap(x2box, y2box);
bmp = img.Clone(new Rectangle(x1box, y1box, x2box, y2box), eventArgs.Frame.PixelFormat);
Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
Bitmap img1 = filter.Apply(bmp);
Threshold tresh = new Threshold((int)tresh1); // tresh1 is 0-255 but is set to zero here
tresh.ApplyInPlace(img1);
int iterator = 1; int xrow = 0; // here i use these constant to calculate location of the pixels
byte[] arraybyte = BitmapToByteArray(img1);
for (int i = 0; i < arraybyte.Length; i++)
{
if (i - iterator * img1.Width == 0)
{
xrow++;
iterator++;
}
if (arraybyte[i] == 0) // if pixel is black
{
X_val.Add(i - xrow * img1.Width);
Y_val.Add(iterator);
}
}
for (int i = 0; i < X_val.Count; i++)
{
YAve += Y_val[i];
XAve += X_val[i];
}
MessageBox.Show(X_val.Count.ToString()); // shows non-zero value!
the BitmapToByteArray method is as follow:
public static byte[] BitmapToByteArray(Bitmap bitmap)
{
BitmapData bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
int numbytes = bmpdata.Stride * bitmap.Height;
byte[] bytedata = new byte[numbytes];
IntPtr ptr = bmpdata.Scan0;
Marshal.Copy(ptr, bytedata, 0, numbytes);
bitmap.UnlockBits(bmpdata);
return bytedata;
}
The number of bytes for each row of the Bitmap will be enforced to be a multiple of 4. If roi width * bytes per pixel is not a multiple of 4, you will have padding bytes at the end of each row.
They will not be thresholded as they aren't really part of the Bitmap, so their value may be 0. Your BitmapToByteArray method might not be padding-aware and read every byte.
I have a program which gets every pixel in an image and outputs them into a text document. The problem is that it just throws them in there are as numbers and it looks rather ugly.
e.i. " 37 37 37 36" I wish to display them much like the GetPixel function does.
e.i. [A=255, R=4, G=255, B=131]. My current code is...
Bitmap bmp = new Bitmap("Space big.jpg");
Rectangle bmpRec = new Rectangle(0, 0, bmp.Width, bmp.Height); //Creates Rectangle for holding picture
BitmapData bmpData = bmp.LockBits(bmpRec, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); //Gets the Bitmap data
IntPtr Pointer = bmpData.Scan0; //Sets pointer
int DataBytes = Math.Abs(bmpData.Stride) * bmp.Height; //Gets array size
byte[] rgbValues = new byte[DataBytes]; //Creates array
Marshal.Copy(Pointer, rgbValues, 0, DataBytes); //Copies of out memory
bmp.UnlockBits(bmpData);
StringBuilder Pix = new StringBuilder(" ");
Stopwatch Timer = new Stopwatch();
pictureBox1.Image = bmp;
StringBuilder EachPixel = new StringBuilder("");
Timer.Start();
for (int i = 0; i < bmpData.Width; i++)
{
for (int j = 0; j < bmpData.Height; j++)
{
// compute the proper offset into the array for these co-ords
var pixel = rgbValues[i + j * Math.Abs(bmpData.Stride)];
Pix.Append(" ");
Pix.Append(pixel);
}
}
I have an array which consists in PixelData extracted from a Dicom Image.
Here's the code:
byte[] bytes = img.PixelData.GetFrame(0).Data; // img is the Dicom Image
int count = bytes.Length / 2;
ushort[] words = new ushort[count];
for (int i = 0, p = 0; i < count; i++, p += 2)
{
words[i] = BitConverter.ToUInt16(bytes, p);
}
pixels16 = words.ToList(); //pixels16 contains now the PixelData for the Grayscale image
Now, here's my question, how do I render that into a Picturebox??
My code for converting Bitmaps from Format16bppGrayScale to Format8bppIndexed format. PictureBox can easy show this format. (If you want, you can use different palette).
public Bitmap Gray16To8bppIndexed(Bitmap BmpIn)
{
if (BmpIn.PixelFormat != PixelFormat.Format16bppGrayScale)
throw new BadImageFormatException();
byte[] ImageData = new byte[BmpIn.Width * BmpIn.Height * 2];
Rectangle Re = new Rectangle(0, 0, BmpIn.Width, BmpIn.Height);
BitmapData BmpData = BmpIn.LockBits(Re, ImageLockMode.ReadOnly, BmpIn.PixelFormat);
Marshal.Copy(BmpData.Scan0, ImageData, 0, ImageData.Length);
BmpIn.UnlockBits(BmpData);
byte[] ImageData2 = new byte[BmpIn.Width * BmpIn.Height];
for (long i = 0; i < ImageData2.LongLength; i++)
ImageData2[i] = ImageData[i * 2 + 1];
ImageData = null;
Bitmap BmpOut = new Bitmap(BmpIn.Width, BmpIn.Height, PixelFormat.Format8bppIndexed);
BmpData = BmpOut.LockBits(Re, ImageLockMode.WriteOnly, BmpOut.PixelFormat);
Marshal.Copy(ImageData2, 0, BmpData.Scan0, ImageData2.Length);
BmpOut.UnlockBits(BmpData);
ImageData2 = null;
BmpData = null;
ColorPalette GrayPalette = BmpOut.Palette;
Color[] GrayColors = GrayPalette.Entries;
for (int i = 0; i < GrayColors.Length; i++)
GrayColors[GrayColors.Length - 1 - i] = Color.FromArgb(i, i, i);
BmpOut.Palette = GrayPalette;
return BmpOut;
}
Well, I don't know the specifics, because it depends on how you really want to go about it (if performance is important, you need to create your own subclass of Bitmap, but otherwise, Bitmap.SetPixel would work fine).
But essentially, you need to shove those pixels into a Bitmap, then set the picture box's image to that bitmap, like:
Bitmap bitmap = new Bitmap(width, height);
for(int y = 0;y < height;y++)
for(int x = 0;x < width;x++)
bitmap.SetPixel(x,y, Color.fromRGB(/* unpack your R,G,B channel of your pixel here */);
pictureBox.Image = bitmap;
You can utilize the AForge .NET Framework, which is a great .NET library for image processing. The built-in .NET Picturebox could not nativley display images with System.Drawing.Imaging.PixelFormat.Format16bppGrayScale, but the AForge library has its own Picturebox control, check this out. It expects a .NET Image.
You can include AForge to your project easily with NuGet:
Install-Package AForge.Controls
Install-Package AForge.Imaging
Or just
Install-Package AForge
Example code below:
//SOME BYTES
//Load here the DICOM image
int width=640, height=480;
int numberOfPixels = width*height;
byte[] source = new byte[2*numberOfPixels];
//With AFORGE
var image = AForge.Imaging.UnmanagedImage.Create(width, height, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);
IntPtr ptrToImage = image.ImageData;
//Copies the bytes from source to the image
//System.Runtime.InteropServices
Marshal.Copy(source, 0, ptrToImage,numberOfPixels);
//WITH .NET
System.Drawing.Bitmap bitmapImage = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);
var imageData = bitmapImage.LockBits(new System.Drawing.Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);
Marshal.Copy(source, 0, imageData.Scan0, numberOfPixels);
bitmapImage.UnlockBits(imageData);
Got this idea from a friend. The inputImage.ImageSource property is a 2D array with grayscale pixel values.
Bitmap grayscaleImage = new Bitmap(inputImage.ImageSource);
for (int x = 0; x < grayscaleImage.Width; x++)
{
for (int y = 0; y < grayscaleImage.Height; y++)
{
byte[,] tempMatrix = inputImage.ImageGrayscale;
byte temp = tempMatrix[x, y];
Color tempColor = Color.FromArgb(255, temp, temp, temp);
grayscaleImage.SetPixel(x, y, tempColor);
}
}
picboxDisplay.Image = grayscaleImage;
I'm trying to test whether writing individual images or a bundle zipped is quicker. My approach is to create a random byte array of values between 0 and 255 (8-bit image) and form a Bitmap from it, writing repeatedly using Bitmap.Save. In this way I can set the PixelFormat to Format8bppIndexed, which gives a grayscale image:
// Random number Generator
Random rnd = new Random();
// Create a single image
int Width = 640;
int Height = 512;
var b = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);
ColorPalette ncp = b.Palette;
for (int i = 0; i < 256; i++)
ncp.Entries[i] = Color.FromArgb(255, i, i, i);
b.Palette = ncp;
var BoundsRect = new Rectangle(0, 0, Width, Height);
BitmapData bmpData = b.LockBits(BoundsRect,
ImageLockMode.WriteOnly,
b.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = bmpData.Stride * b.Height;
var rgbValues = new byte[bytes];
// fill in rgbValues, e.g. with a for loop over an input array
rnd.NextBytes(rgbValues);
Marshal.Copy(rgbValues, 0, ptr, bytes);
b.UnlockBits(bmpData);
// copy image to a list of ~1000
List<Bitmap> bmps = new List<Bitmap>();
for (int i = 0; i < 500; i++)
bmps.Add(new Bitmap(b));
// Write to individual files
DateTime t0=DateTime.Now;
for (int i=0;i<bmps.Count;i++)
b.Save(#"C:\Temp\DiskTransferTest\IndividualImages\" + i.ToString() + ".bmp");
DateTime t1=DateTime.Now;
Console.WriteLine("Time to write individually: " + (t1-t0).ToString());
After that, I try and zip them all into a single ZIP file and save, using DotNetZip. This works, but I get a colour image rather than a greyscale one, so the filesizes are much larger.
// Create memorystreams from bitmap to pass to DotNetZip
List<MemoryStream> mss = new List<MemoryStream>();
for (int i = 0; i < bmps.Count; i++)
{
mss.Add(new MemoryStream());
bmps[i].Save(mss[i], ImageFormat.Bmp);
mss[i].Seek(0, SeekOrigin.Begin);
}
// Compress and write
t0 = DateTime.Now;
using (ZipFile zipfile = new ZipFile())
{
zipfile.CompressionLevel = 0;
int i=0;
foreach (MemoryStream ms in mss)
{
string pictureName = i.ToString() + ".bmp";
zipfile.AddEntry(pictureName,ms);
i++;
}
zipfile.Save(#"C:\Temp\DiskTransferTest\zipped.zip");
}
t1 = DateTime.Now;
Console.WriteLine("Time to write compressed: " + (t1 - t0).ToString());
Any suggestions on how to write a greyscale to the zip via the MemoryStream?
The problem is that your new bitmaps aren't 8bpp bitmaps. Consider your code:
// copy image to a list of ~1000
List<Bitmap> bmps = new List<Bitmap>();
for (int i = 0; i < 500; i++)
bmps.Add(new Bitmap(b));
// Write to individual files
DateTime t0=DateTime.Now;
for (int i=0;i<bmps.Count;i++)
b.Save(#"C:\Temp\DiskTransferTest\IndividualImages\" + i.ToString() + ".bmp");
The bitmap b is an 8bpp bitmap. You're writing it to file. But if you examine bmps[0] I think you'll find that the PixelFormat is 32bpp. At least, that's what happens when I execute this code:
var bmp = new Bitmap(640, 480, PixelFormat.Format8bppIndexed);
Console.WriteLine(bmp.PixelFormat); // 8 bpp
var bmp2 = new Bitmap(bmp);
Console.WriteLine(bmp2.PixelFormat); // 32 bpp
In your code that writes the bitmaps to the memory stream, you're accessing bmps[i], rather than the 8bpp image, b, as you are when writing to file.
You need to create your list bitmaps, set their properties, and then copy b. You can't duplicate the bitmaps with their properties with the new Bitmap(b) constructor call.
As far as I know, it's not possible to create a true grayscale image using the Bitmap class. You would have to exchange the Bitmap.Palette property from the present ColorPalette to a GrayscalePalette that would only store one byte per color instead of the four bytes needed per ARGB color. The framework does not contain any such class, ColorPalette does not inherit a base class or implement an interface, and it's also sealed so you can't inherit from the class either.
On the other hand, checking the bitmap file format specification I see that there is no way to save a true grayscale bitmap image (using a 256 byte color table). Saving an 8 bit grayscale image in Photoshop CS6 and then opening it again shows that it was saved as a 8 bit color indexed image (although R = G = B for all colors in the palette).