|
|
I'm pulling in images from a camera at around 300 frames a second and i'm stitching them together. I'm not even close to being able to keep up with the camera currently. I was hoping I could get some of the experts here at CodeProject to review my code and point out anyways to make the process faster.
Thanks
Bitmap currentStitch = null;
private void StitchImage(object sender)
{
for (int i = 0; i < FrameList.Count; i++)
{
if (currentStitch == null) currentStitch = (Bitmap)FrameList[i].Clone();
else
AddCurretnSlice(FrameList[i]);
}
}
private void AddCurretnSlice(Bitmap Slice)
{
Bitmap newStitch = new Bitmap(currentStitch.Width + Slice.Width, Slice.Height);
BitmapData destinationData = newStitch.LockBits(new Rectangle(0, 0, newStitch.Width, newStitch.Height), ImageLockMode.ReadWrite, currentStitch.PixelFormat);
BitmapData overlayData = currentStitch.LockBits(new Rectangle(0, 0, currentStitch.Width, currentStitch.Height), ImageLockMode.ReadOnly, currentStitch.PixelFormat);
BitmapData sourceData = Slice.LockBits(new Rectangle(0, 0, Slice.Width, Slice.Height), ImageLockMode.ReadOnly, Slice.PixelFormat);
try
{
int dstHeight = destinationData.Height;
int src1Height = overlayData.Height;
int src2Height = sourceData.Height;
int src1Stride = overlayData.Stride;
int src2Stride = sourceData.Stride;
int dstStride = destinationData.Stride;
int pixelSize = System.Drawing.Image.GetPixelFormatSize(sourceData.PixelFormat) / 8;
int copySize1 = overlayData.Width * pixelSize;
int copySize2 = sourceData.Width * pixelSize;
unsafe
{
byte* src1 = (byte*)overlayData.Scan0.ToPointer();
byte* src2 = (byte*)sourceData.Scan0.ToPointer();
byte* dst = (byte*)destinationData.Scan0.ToPointer();
for (int y = 0; y < dstHeight; y++)
{
if (y < src1Height)
AVision.SystemTools.CopyUnmanagedMemory(dst, src1, copySize1);
if (y < src2Height)
AVision.SystemTools.CopyUnmanagedMemory(dst + copySize1, src2, copySize2);
src1 += src1Stride;
src2 += src2Stride;
dst += dstStride;
}
}
}
finally
{
newStitch.UnlockBits(destinationData);
currentStitch.UnlockBits(overlayData);
Slice.UnlockBits(sourceData);
currentStitch.Dispose();
currentStitch = newStitch;
}
}
|
|
|
|
|
IMHO, you're better off saving the images to disk and stitching them in a separate thread (or even process).
/ravi
|
|
|
|
|
Thanks for the response, but that's not possible. The requirement is to keep up with the camera in real time.
|
|
|
|
|
The requirement is not technically possible under Windows. It is not a real-time system, and the OS may decide to freeze your threads when it sees fit. I'd also like to point out that 300 bitmaps/second would be a bit "much". How big is a single image? And how many seconds worth of those bitmaps would fit into the available memory?
Bastard Programmer from Hell
If you can't read my code, try converting it here[^]
|
|
|
|
|
Thanks for your reply, but I don't believe it's not possible. I'm up to processing a little over 200/second and I've never heard of the OS deciding to freeze my thread on it's own when it sees fit. Sounds like somebody might be used to blaming windows for shody code.
These bitmaps are from a line-scan camera, which means they are at most 10 pixels across. This has to be doable. I've got it dumping to an array now and another thread picking images up from that array. This works, but my stitch is a few seconds behind my camera when a record event fires. I believe i can get it to real time.
|
|
|
|
|
JBHowl wrote: Sounds like somebody might be used to blaming windows for shody code. Now, I don't want to split the thread into fibers, but..
http://en.wikipedia.org/wiki/Preemption_(computing)[^]
JBHowl wrote: This works, but my stitch is a few seconds behind my camera when a record event
fires. I believe i can get it to real time. You can push your computer to its limits, that is true. It will depend on your defenition of "real time", as Windows is no such OS, but still very powerfull - even without writing a new dedicated device-driver in an unmanaged language.
Can you store the "last" picture in a different place, and use that as a source for painting the display? A memory-mapped file might help - any fixed-size memory buffer that does not move around in memory would do.
Bastard Programmer from Hell
If you can't read my code, try converting it here[^]
|
|
|
|
|
Instead of re-allocating memory for every "slice", I would look into pre-allocating enough memory for a "graphics" object that can hold all expected slices, and then overlaying / merging this with the slices; cropping the final result if necessary.
(see: Graphics class; FromImage() and DrawImage()).
Easy enough to benchmark without the camera (and third-party software).
|
|
|
|
|
Hey Gerry, thanks! That's exactly what I did and it works great except every now and then I run out of memory allocating my blank bitmap. You have to have enough continuous memory available and sometimes it errors out. See below, any thoughts?
bmp = new Bitmap(100000, FrameHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
bmpG = Graphics.FromImage(bmp);
private void frameevent_newFrame(bitmap frame)
{
}
any ideas?
|
|
|
|
|
"Bitmap" is an IDisposable.
When working with "images" (or any IDisposable), particularly a lot of them, you need to insure that they are disposed of in a timely manner; e.g.
using ( Bitmap bmp = new Bitmap ... ) {
:
:
}
|
|
|
|
|
While not this exact problem (no camera was involved), I have stitched together map tiles for displaying in an application, that has relied on rapidly creating the images. If you know what the dimensions of the end image is going to be beforehand, then you can use Parallel.For to process the images and map them into the appropriate offset without difficulty.
|
|
|
|
|
sir,
can you please help me to run this project in visual 2010. do i need to install some plugins.. if yes then please guide. i'm not able to run it.
|
|
|
|
|
|
What in the hello world are you talking about?
There are only 10 types of people in the world, those who understand binary and those who don't.
|
|
|
|
|
Based on your earlier posts I am assuming you mean how can you run the code from the article A Simple C# Genetic Algorithm[^]
Please don't cross-post - you have already posted a question to the author in the correct forum - he is probably at work at the moment and can't respond immediately.
In answer to your question, you need to download the source and load it into Visual Studio. It is highly likely that VS will need to convert the source to the version that you are using - allow the automatic conversion to run through (it should not produce any errors).
Once you have the converted solution open,
- open up the Solution Explorer (Ctrl-Alt-L)
- right-click on the project name "GATest" and select "Set as Startup project"
- you can now run the code
|
|
|
|
|
Hello Gurus:
I'm having a difficult time selecting an item on a dropdown list using C# webbrowser controller. The website have this html code:
<select name="Searchbar_Inventory$ddl_condition" onchange="javascript:setTimeout('__doPostBack(\'Searchbar_Inventory$ddl_condition\',\'\')', 0)" id="Searchbar_Inventory_ddl_condition" style="width:90%;">
<option selected="selected" value="Product Name">Product Name</option>
<option value="Office">Office</option>
<option value="Product Category">Product Category</option>
<option value="Product Sub Category">Product Sub Category</option>
<option value="Received Date">Received Date</option>
<option value="Order No.">Order No.</option>
<option value="Sub Order Type">Sub Order Type</option>
<option value="Account Name">Account Name</option>
<option value="Status">Status</option>
<option value="Grade">Grade</option>
<option value="Serial No.">Serial No.</option>
<option value="RTS">RTS</option>
<option value="TDM">TDM</option>
<option value="Bus ID">Bus ID</option>
<option value="Traveler ID">Traveler ID</option>
<option value="Old Package No.">Old Package No.</option>
<option value="Product Properties">Product Properties</option>
<option value="Dismantle Date">Dismantle Date</option>
<option value="Heat/Job No.">Heat/Job No.</option>
<option value="RMA No.">RMA No.</option>
</select>
so part of my code to select a value was this:
webBrowserAdjustment.Document.GetElementById("Searchbar_Inventory_dll_condition").SetAttribute("value", "Traveler ID");
And it did not work...can you help me?
Elmer
|
|
|
|
|
|
I was looking at this article[^] on Threads vs. Tasks.
The end of the first paragraph says
Thread allows the highest degree of control; you can Abort() or Suspend() or Resume() a thread (though this is a very bad idea)
It is a bad idea and if so, why?
Thanks
If it's not broken, fix it until it is
|
|
|
|
|
The problem with suspending a thread is that you have no real idea of what it's doing at the point you suspend it. Imagine you have a process where you read a load of data into memory and then use this to do a bulk insert into a database on a thread and you've taken a lock on a table. Your code issues a suspend statement just after you've started writing to the database - you've now locked the table indefinitely. There are other cases where this technique is equally bad - you've suspended inside a locked mutex for instance.
The case against Abort is just as bad. The big problem with it is that it kills the thread at that point (that's the theory anyway - while it's not trivial, it is possible to produce code that prevents a thread being aborted - malware writers are fond of this). You have no idea what's going on at that point, so you have no idea whether the exit has left resources in a clean state or not.
|
|
|
|
|
Good answer. Thanks
If it's not broken, fix it until it is
|
|
|
|
|
Ok this is a CONCEPT problem (the code is not professional, just read it for the concept, though it does compile and run).
Right, I have asked many people on IRC and no one is giving me a straight answer lol so maybe some professionals can help me.
I am trying to use a Datasource property for my combobox. This is so I can just pass an entire KeyValue pair with indexes and values to be shown in the listbox.
Creating the keyvaluepair as a WHOLE and passing that to datasource property works perfectly.
Creating a keyvaluepair PROCEDURALLY (in a loop or w/e) ends in the datasource property ToString()ing it, list box items show BOTH value AND display members in square brackets as an entire option.
NOTE: Getting the lsitbox.value results in ONLY the valuemember being returned even though the listbox shows both the valuemembers AND the displaymember? it knows they are different?
Now, I dont want any alternatives or comments about the quality of the code, all I want to know is WHY is it doing this, it seems compeltely illogical that the same TYPE and Values and can different Effects within the datasource property? Its like it knows it was built procedurally?
Anyway here is some code for reference:
namespace skbtInstaller
{
public partial class frmMainWindow : Form
{
public frmMainWindow()
{
InitializeComponent();
this.cBoxArmaPath.ValueMember = "Key";
this.cBoxArmaPath.DisplayMember = "Value";
}
public void addServerPathItem(String Key, String Value)
{
this.cBoxArmaPath.Items.Add(new KeyValuePair<String, String>(Key,Value));
}
public void setServerPathDatasource(List<KeyValuePair<String, String>> source)
{
this.cBoxArmaPath.DataSource = new BindingSource(source, null);
}
}
public class skbtServerControl
{
private frmMainWindow frmMainWindowHandle;
public void refreshformWindow()
{
this.frmMainWindowHandle.clearPathBox();
foreach (KeyValuePair<String, skbtServerConfig> pair in CoreConfig.getServerConfigList())
{
this.frmMainWindowHandle.addServerPathItem(pair.Key, pair.Value.getTextualName());
}
}
public void refreshformWindowWithSTATICDatasource()
{
this.frmMainWindowHandle.clearPathBox();
var pathDataSource = new List<KeyValuePair<String, String>>()
{
new KeyValuePair<String, String>("testKey1", "somevalue1"),
new KeyValuePair<String, String>("testKey2", "somevalue2"),
new KeyValuePair<String, String>("testKey3", "somevalue3")
};
this.frmMainWindowHandle.setServerPathDatasource(pathDataSource);
}
public void refreshformWindowWithDatasource()
{
this.frmMainWindowHandle.clearPathBox();
var pathDataSource = new List<KeyValuePair<String, String>>();
foreach (KeyValuePair<String, skbtServerConfig> pair in CoreConfig.getServerConfigList())
{
pathDataSource.Add(new KeyValuePair<String, String>(pair.Key, pair.Value.getTextualName()));
}
this.frmMainWindowHandle.setServerPathDatasource(pathDataSource);
}
}
}
Chemical Bliss
aka Uniflare
|
|
|
|
|
uniflare wrote: comments about the quality of the code
No worries, mate. We've seen worse!
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
When you put a breakpoint on the setServerPathDataSource for the problem code, what do you see in pathDataSource? How does it compare to the one that works? Do those objects actually appear identical internally?
|
|
|
|
|
Ok did some debugging, it seems that when i set a new datasource, the combobox.DisplayMember defaults to "". Although the combobox.ValueMember stays as "Key".
the fix Im using is to re-set DisplayMember to "Value" on each datasource update.
Is this a bug? Is it supposed to reset these properties when the datasource is set? or is it my implementation that is buggy?
All other factors are identical. The actual problem was only the very FIRST datasource set worked as intended, every subsequent datasource was displayed improperly with displaymembers and valuemember being shown in the listbox.
Revised Code Example: (Working with fix) (Compiles and runs with the components on designer..)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace etstapp1
{
public partial class Form1 : Form
{
private skbtServerControl sc;
public Form1()
{
InitializeComponent();
this.sc = new skbtServerControl(this);
this.cBoxArmaPath.ValueMember = "Key";
this.cBoxArmaPath.DisplayMember = "Value";
}
public void addServerPathItem(String Key, String Value)
{
this.cBoxArmaPath.Items.Add(new KeyValuePair<String, String>(Key, Value));
}
public void setServerPathDatasource(List<KeyValuePair<String, String>> source)
{
this.cBoxArmaPath.DisplayMember = "Value";
this.cBoxArmaPath.ValueMember = "Key";
this.cBoxArmaPath.DataSource = new BindingSource(source, null);
}
public void clearPathBox()
{
if(this.cBoxArmaPath.DataSource == null){
this.cBoxArmaPath.Items.Clear();
}
else
{
this.cBoxArmaPath.DataSource = null;
}
}
private void btnStatic_Click(object sender, EventArgs e)
{
this.sc.refreshformWindowWithSTATICDatasource();
}
private void btnNormal_Click(object sender, EventArgs e)
{
this.sc.refreshFormWindow();
}
private void btnDynamic_Click(object sender, EventArgs e)
{
this.sc.refreshformWindowWithDatasource();
}
}
public class skbtServerControl
{
private CoreConfig CoreConfig;
private Form1 frmMainWindowHandle;
public skbtServerControl(Form1 f){
this.frmMainWindowHandle = f;
this.CoreConfig = new CoreConfig();
}
public void refreshFormWindow()
{
this.frmMainWindowHandle.clearPathBox();
foreach (KeyValuePair<String, skbtServerConfig> pair in CoreConfig.getServerConfigList())
{
this.frmMainWindowHandle.addServerPathItem(pair.Key, pair.Value.getTextualName());
}
}
public void refreshformWindowWithSTATICDatasource()
{
this.frmMainWindowHandle.clearPathBox();
var pathDataSource = new List<KeyValuePair<String, String>>()
{
new KeyValuePair<String, String>("testKey1", "somevalue1"),
new KeyValuePair<String, String>("testKey2", "somevalue2"),
new KeyValuePair<String, String>("testKey3", "somevalue3")
};
this.frmMainWindowHandle.setServerPathDatasource(pathDataSource);
}
public void refreshformWindowWithDatasource()
{
this.frmMainWindowHandle.clearPathBox();
var pathDataSource = new List<KeyValuePair<String, String>>();
foreach (KeyValuePair<String, skbtServerConfig> pair in this.CoreConfig.getServerConfigList())
{
pathDataSource.Add(new KeyValuePair<String, String>(pair.Key, pair.Value.getTextualName()));
}
this.frmMainWindowHandle.setServerPathDatasource(pathDataSource);
}
}
public class CoreConfig
{
public Dictionary<String, skbtServerConfig> getServerConfigList(){
return new Dictionary<string, skbtServerConfig>()
{
{"somekey1", new skbtServerConfig("somename1")},
{"somekey2", new skbtServerConfig("somename2")}
};
}
}
public class skbtServerConfig
{
private String name;
public skbtServerConfig(String name)
{
this.name = name;
}
public String getTextualName()
{
return this.name;
}
}
}
Thanks guys, its not pretty and probably not the way to use datasources but at least i know what the problem was and the solution.
Though, If anyone can tell me why its resetting the members?
modified 17-Feb-15 16:32pm.
|
|
|
|
|
This would seem to indicate that the new BindingSource was resetting the values. While it may not be a bug, it's certainly not a very clever API design (by the designers of WinForms, not you I will hasten to add).
Okay, I've been having more of a think about this and I suspect that the reason that you need to do this is because when you change your BindingSource, the ComboBox has no concept of what it's bound to. You could be binding to something that doesn't use Key and Value here, so it forces you to update these values here.
modified 17-Feb-15 16:58pm.
|
|
|
|
|