I've been working for a few weeks on grabbing a webcam image and rendering it on a windows form, but have been struggeling with speed issues the entire time. I need at least a frame rate of 10 Hz to be able to update my background process.
I started of using a pictureBox but the solution I've ended up with is to create a XNA-panel inside my Form, and then rendering the image as a background-sprite by converting the bitmap into a Texture2D using a script I found here.
The problem I've encountered now and have not been able to solve is; when I load a Bitmap in the code by calling the bitmap constructor like below, everything runs smoothly and I can get high fps. This is what I did during testing and was very happy with the results.
Bitmap image = new Bitmap(320, 240);
But as soon as I send the bitmap that I grab from the webcam it takes a lot longer to render for some reason I can not fathom. To my knowledge of bitmaps the images are the same format, it's just the colour of the pixels that are different. What I checked for format is size (320*240), resolution (96) and pixel format (Format32bppArgb). Am I missing anything?
This is how I grab the image from the webcam:
VideoCaptureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
FinalVideoSource = new VideoCaptureDevice(VideoCaptureDevices[0].MonikerString);
FinalVideoSource.DesiredFrameSize = new Size(320, 240);
FinalVideoSource.DesiredFrameRate = fps;
FinalVideoSource.NewFrame += new NewFrameEventHandler(FinalVideoSource_NewFrame);
void FinalVideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
// create bitmap from frame
image = eventArgs.Frame.Clone(new Rectangle(0, 0, 320, 240), PixelFormat.Format32bppArgb);
...
This is my draw function in XNA:
protected override void Draw()
{
backTexture = GetTexture(GraphicsDevice, image);
GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue);
// TODO: Add your drawing code here
sprites.Begin();
Vector2 pos = new Vector2(0, 0);
sprites.Draw(backTexture, pos, Microsoft.Xna.Framework.Color.White);
sprites.End();
}
private Texture2D GetTexture(GraphicsDevice dev, System.Drawing.Bitmap bmp)
{
int[] imgData = new int[bmp.Width * bmp.Height];
Texture2D texture = new Texture2D(dev, bmp.Width, bmp.Height);
unsafe
{
// lock bitmap
System.Drawing.Imaging.BitmapData origdata =
bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
uint* byteData = (uint*)origdata.Scan0;
// Switch bgra -> rgba
for (int i = 0; i < imgData.Length; i++)
{
byteData[i] = (byteData[i] & 0x000000ff) << 16 | (byteData[i] & 0x0000FF00) | (byteData[i] & 0x00FF0000) >> 16 | (byteData[i] & 0xFF000000);
}
// copy data
System.Runtime.InteropServices.Marshal.Copy(origdata.Scan0, imgData, 0, bmp.Width * bmp.Height);
byteData = null;
// unlock bitmap
bmp.UnlockBits(origdata);
}
texture.SetData(imgData);
return texture;
}
I would be very grateful if someone could help me with this since I'm stuck now. The community here has been great and I managed to get this far without asking before, which is amazing since I have no prior experience with C# or XNA. With that in mind I realize I might be missing something simple or just approaching this the wrong way.
I've narrowed it down to the bitmap image loading. The only thing I change when using the newly constructed bitmap is to simply overwrite the one from the webcam before processing it in XNA.
My question right now is really, am I missing something with how the bitmaps are constructed which can explain the big difference in rendering speed? Is the conversion to Texture2D the problem here? But I don't see how a different image could affect the speed of that conversion.
Ok. I don't know what is the problem exactly. But I can give you some comments.
-First, set the frame rate of the XNA game equal or less that your webcam fps.
By default XNA run at 60fps, so you are calling the GetTexture() method twice for each frame of your webcam if you are using 30fps.
In the Initialize code:
TargetElapsedTime = TimeSpan.FromSeconds(1f/webcam fps)
If that don't work...
you can try this code to convert from bitmap to texture.
protected override void Draw()
{
//Unset the texture from the GraphicsDevice
for (int i = 0; i < 16; i++)
{
if (Game.GraphicsDevice.Textures[i] == backTexture)
{
Game.GraphicsDevice.Textures[i] = null;
break;
}
}
backTexture.SetData<byte>(image.GetBytes());
GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue);
// TODO: Add your drawing code here
sprites.Begin();
Vector2 pos = new Vector2(0, 0);
sprites.Draw(backTexture, pos, Microsoft.Xna.Framework.Color.White);
sprites.End();
}
public static byte[] GetBytes(this Bitmap bitmap)
{
var data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
// calculate the byte size: for PixelFormat.Format32bppArgb (standard for GDI bitmaps) it's the hight * stride
int bufferSize = data.Height * data.Stride; // stride already incorporates 4 bytes per pixel
// create buffer
byte[] bytes = new byte[bufferSize];
// copy bitmap data into buffer
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
// unlock the bitmap data
bitmap.UnlockBits(data);
return bytes;
}
I was testing this code and works fine. Hope this help.
Related
I'm currently loading and saving Texture2Ds used as mapped images into a database so they can be preloaded later. Each color channel needs to be the precise color it was when saved after loading. The problem is Texture2D.FromStream sometimes returns incorrect color channels that are sometimes off by 1 or so. This is unacceptable as the mapped colors are useless if they are incorrect.
The example below provides a situation where an RGB of 255 is changed to an RGB of 254 when the alpha is set at 100. When setting the alpha to 1 or 255 they return as 255 correctly, other alpha values cause the same issue as 100.
Texture2D tex = new Texture2D(GraphicsDevice, 20, 20, false, SurfaceFormat.Color);
Color[] data = new Color[tex.Width * tex.Height];
for (int i = 0; i < data.Length; i++) {
data[i] = new Color(255, 255, 255, 100);
}
tex.SetData<Color>(data);
using (Stream stream = File.Open("TestAlpha.png", FileMode.OpenOrCreate)) {
tex.SaveAsPng(stream, tex.Width, tex.Height);
stream.Position = 0;
tex = Texture2D.FromStream(GraphicsDevice, stream);
tex.GetData<Color>(data);
Console.WriteLine(data[0]); // Returns (R:254 G:254 B:254 A:100)
}
I have confirmed the png has the correct RGB of 255 when looking at the saved image in Paint.NET so it can only be something caused during Texture2D.FromStream.
Well, I did not find the cause of Texture2D.FromStream's issue but I found an all-around better way to load Texture2Ds. In this case I load GDI Bitmap from the stream instead, take its data, and transfer it over to a newly created Texture2D.
I plan on using this whether or not Texture2D.FromSteam is fixable but I'd still love to know if anyone knows what's going on with it.
For those of you who are new, make sure to include System.Drawing as a reference as its not present in XNA projects by default.
public static unsafe Texture2D FromStream(GraphicsDevice graphicsDevice, Stream stream) {
// Load through GDI Bitmap because it doesn't cause issues with alpha
using (Bitmap bitmap = (Bitmap) Bitmap.FromStream(stream)) {
// Create a texture and array to output the bitmap to
Texture2D texture = new Texture2D(graphicsDevice,
bitmap.Width, bitmap.Height, false, SurfaceFormat.Color);
Color[] data = new Color[bitmap.Width * bitmap.Height];
// Get the pixels from the bitmap
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
// Write the pixels to the data buffer
byte* ptr = (byte*) bmpData.Scan0;
for (int i = 0; i < data.Length; i++) {
// Go through every color and reverse red and blue channels
data[i] = new Color(ptr[2], ptr[1], ptr[0], ptr[3]);
ptr += 4;
}
bitmap.UnlockBits(bmpData);
// Assign the data to the texture
texture.SetData<Color>(data);
// Fun fact: All this extra work is actually 50% faster than
// Texture2D.FromStream! It's not only broken, but slow as well.
return texture;
}
}
How can I playback and save selected raw video frames from raw video data (in .rgb or .raw).
Raw uncompressed video data is 320 x 240 in grayscale format with 1 byte/pixel and just 30 fps.
But how can I view multiple the frames in 30 FPS?
Can I use DirectShow or a similar API? And what is the best resource to get started? I looked at FFPMEG but I want to avoid showing compressed frames as this it is for scientific application.
So far I have been able to view one frame from a raw video data (in .rgb) using this code in C# .NET for Win Forms using a Picture Box control.
But not sure how to do multiple frames and perhaps have a seeking control.
byte[] imageData = File.ReadAllBytes("output.rgb");
Console.WriteLine("imageDataLen=" + imageData.Length);
int width = 320;
int height = 240;
var bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
//bmp = (Bitmap) ConvertToGrayScale(bmp);
ColorPalette pal = bmp.Palette;
for (int i = 0; i <= 255; i++)
{
// create greyscale color table
pal.Entries[i] = Color.FromArgb(i, i, i);
}
bmp.Palette = pal;
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0,
bmp.Width,
bmp.Height),
ImageLockMode.WriteOnly,
bmp.PixelFormat);
Marshal.Copy(imageData, 0, bmpData.Scan0, width * height);
bmp.UnlockBits(bmpData);
pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
pictureBox1.Image = bmp;
I am creating placeholder images in certain sizes that will be used as Data URIs
Bitmap bitmap = new Bitmap(16, 10);
I have done some research, but can't find a good way of saving this bitmap as the smallest possible filesize, which is why I want an 8bit PNG.
My question is: How can I save this bitmap into a file/bytearray/stream as an 8bit PNG? Any good libraries?
You can do this with nQuant (which you can install with nuget, or see references below). The following example converts an image on disk and would be readily adapted to meet your needs.
public static bool TryNQuantify(string inputFilename, string outputFilename)
{
var quantizer = new nQuant.WuQuantizer();
var bitmap = new Bitmap(inputFilename);
if (bitmap.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb)
{
ConvertTo32bppAndDisposeOriginal(ref bitmap);
}
try
{
using (var quantized = quantizer.QuantizeImage(bitmap))
{
quantized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
}
catch
{
return false;
}
finally
{
bitmap.Dispose();
}
return true;
}
private static void ConvertTo32bppAndDisposeOriginal(ref Bitmap img)
{
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var gr = Graphics.FromImage(bmp))
gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));
img.Dispose();
img = bmp;
}
For more information see:
https://github.com/mcychan/nQuant.cs
https://code.msdn.microsoft.com/windowsdesktop/Convert-32-bit-PNGs-to-81ef8c81
I like the FreeImage project, it is light weight and easy to use. Below is an example of creating a transparent png. You could easily wrap this in a method and set the width and height and transparency value.
//create a new bit map
FIBITMAP dib = new FIBITMAP();
//allocate a 16x10 bitmap with 8bit depth
dib = FreeImage.Allocate(16, 10, 8);
//set the transpareny
byte[] Transparency = new byte[1];
Transparency[0] = 0x00;
FreeImage.SetTransparencyTable(dib, Transparency);
//save the bitmap
FreeImage.Save(FREE_IMAGE_FORMAT.FIF_PNG, dib, "C:\\temp\\tp.png", FREE_IMAGE_SAVE_FLAGS.DEFAULT);
If all you need is a small transparent image, why stop at 8 bit? You can go straight down to 1 bit! You only need one colour anyway, and it'll be even smaller.
In fact, you don't even need to do anything special for that. Since the pixels on a new indexed bitmap will all default to 0, meaning they reference its first palette colour, all you need to do is make a new 1bpp image, and set that first palette colour to transparent:
public static Bitmap MakePlaceholderImage(Int32 width, Int32 height)
{
Bitmap bm = new Bitmap(width, height, PixelFormat.Format1bppIndexed);
// This colour can't be assigned directly since the .Palette getter actually makes a copy of the palette.
ColorPalette pal = bm.Palette;
pal.Entries[0] = Color.Transparent;
bm.Palette = pal;
return bm;
}
I experimented a bit with this, and, saved as png, the end result seemed to consistently be 8 times smaller than the result of the same code executed with 8bpp. For a 5000x5000 image, the file size as png was barely over 3 KiB.
Google search led me to a 6 year old thread and I can't find my solution. It should've been possible 6 years as well, as it uses the System.Drawing Libraray of the .Net Framework.
This example code should help anyone who want to write an 8 bit indexed png.
static class SaveImages
{
public static void SaveGrayImage(Bitmap srcImg, string name)
{
Bitmap grayImg = new Bitmap(srcImg.Width, srcImg.Height, PixelFormat.Format8bppIndexed);
var pal = grayImg.Palette;
foreach (int i in Enumerable.Range(0, 256))
pal.Entries[i] = Color.FromArgb(255, i, i, i);
grayImg.Palette = pal;
var data = grayImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
var bytes = new byte[data.Stride * data.Height];
foreach (int y in Enumerable.Range(0, srcImg.Height))
{
foreach (int x in Enumerable.Range(0, srcImg.Width))
{
var colour = srcImg.GetPixel(x, y);
var c = (int)colour.R + (int)colour.G + (int)colour.B;
c /= 3;
bytes[data.Stride * y + x] = (byte)c;
}
}
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
grayImg.Save(name, ImageFormat.Png);
}
}
Edit: To add transparency dabble with the color palette.
1) Create your bitmap with 8 bit per pixel format:
Bitmap bmp = new Bitmap(20, 20, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
See PixelFormat for more formats.
2) Draw to the bitmap ...
3) Then save it as PNG:
bmp.Save(#"c:\temp\xyz.png", System.Drawing.Imaging.ImageFormat.Png);
The created file will have the same pixel format as bmp (8 bit)
When I make screenshots in my XNA game, each texture.SaveAsPng consumes some memory and it doesn't seem to return back to the game. So eventually I run out of memory. I tried saving the texture data into FileStream and MemoryStream, hoping I could save it from there as Bitmap, but the results are the same. Is there a way to forcefully free this memory or some workaround that will let me get image data and save it some other way without running into out of memory exception?
sw = GraphicsDevice.Viewport.Width;
sh = GraphicsDevice.Viewport.Height;
int[] backBuffer = new int[sw * sh];
GraphicsDevice.GetBackBufferData(backBuffer);
using(Texture2D texture = new Texture2D(GraphicsDevice, sw, sh, false,
GraphicsDevice.PresentationParameters.BackBufferFormat))
{
texture.SetData(backBuffer);
using(var fs = new FileStream("screenshot.png", FileMode.Create))
texture.SaveAsPng(fs, sw, sh); // ← this line causes memory leak
}
You might be able to create the Bitmap from the texture bytes directly and bypass the internal method to check if the SaveAsPng is leaking or its something else.
Try This extension method (unfortunatly I cannot test (no xna at work) but it should work.)
public static class TextureExtensions
{
public static void TextureToPng(this Texture2D texture, int width, int height, ImageFormat imageFormat, string filename)
{
using (Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
byte blue;
IntPtr safePtr;
BitmapData bitmapData;
Rectangle rect = new Rectangle(0, 0, width, height);
byte[] textureData = new byte[4 * width * height];
texture.GetData<byte>(textureData);
for (int i = 0; i < textureData.Length; i += 4)
{
blue = textureData[i];
textureData[i] = textureData[i + 2];
textureData[i + 2] = blue;
}
bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
safePtr = bitmapData.Scan0;
Marshal.Copy(textureData, 0, safePtr, textureData.Length);
bitmap.UnlockBits(bitmapData);
bitmap.Save(filename, imageFormat);
}
}
}
Its a bit crude but you can clean it up (if it even works).
An last but not least (if all other attempts fail) you could call GarbageCollection yourself but this is NOT recommended as its pretty bad practice.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
The above code should be LAST resort only.
Good luck.
public void SaveAsJpeg(Stream stream, int width, int height)
RenderTarget2D.SaveAsJpeg() is missing a parameter to set the Jpeg quality and the output has lots of artifacts. (increasing the size of the image does help a bit but it still looks bad)
Is that parameter hidden somewhere in XNA? How can I improve the quality of the jpeg?
There is no quality parameter in the XNA 4.0 API for encoding JPEG images.
As a very round-about way of doing things, you may be able to create a Silverlight WriteableBitmap, copy the data out of your render target and into that, and then use Extensions.SaveJpeg to save it with a custom quality level.
A better alternative might be to simply save a PNG image.
I will agree with Andrew Russel that a much better alternative would be to simply save to PNG, but if you simply HAVE to save to jpg, then there is a much better way than go trhough WriteableBitmap.
What you can do is create an empty Bitmap object and transfer the texture data over so you will do a Texture2D to Bitmap convertion. Simply copy from below:
public static Bitmap ToBitmap(this Microsoft.Xna.Framework.Graphics.Texture2D rd, int Width, int Height)
{
var Bmp = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
byte[] data = ToBytes(rd);
var bmpData = Bmp.LockBits(new Rectangle(0, 0, rd.Width, rd.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
Bmp.UnlockBits(bmpData);
return Bmp;
}
public static byte[] ToBytes(this Microsoft.Xna.Framework.Graphics.Texture2D rd)
{
byte[] data = new byte[4 * rd.Height * rd.Width];
rd.GetData<byte>(data);
SwapBytes(data);
return data;
}
private static void SwapBytes(byte[] data)
{
System.Threading.Tasks.ParallelOptions po = new System.Threading.Tasks.ParallelOptions();
po.MaxDegreeOfParallelism = -1;
System.Threading.Tasks.Parallel.For(0, data.Length / 4, po, t =>
{
int bi = t * 4;
byte temp = data[bi];
data[bi] = data[bi + 2];
data[bi + 2] = temp;
});
}
Note that the above is pretty efficient, but with certain drawbacks. A similar code to the above can be used to very quickly achieve a Bitmap to Texture2D convertion, but without premultiplying alpha.