开发者

Manipulating an image and updating the pictureBox has some issues

I could not fit exactly what I wanted to say in the title, it would be too long. Okay this is a multi-threaded app. What my app does is looks at a picture, find the edges of the picture, and finds the shape of that object from the edges. While it finds the shape, it constantly updates the image so we can get some sort of visual representation. I have created a very short (40 seconds) video demonstrating the issue: http://phstudios.com/projects/Programming/MultiThreadIssue/

As you can see, everything is working fine until the minute I move the window. This is always the case. I ran the program several times without moving the window and it ran fine. However, the minute I move the window, it will come up with that error. As you see, I am locking the specific image I would like to work with. Is the forms OnPaint overriding that somehow? Is there any way I can fix that?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Drawing.Imaging;

namespace LineRecognition
{
    public enum Shape
    {
        Unknown,
        Quadrilateral,
        Circle,
        Triangle
    }
    public partial class Form1 : Form
    {
        Bitmap image, original, shapes;

        List<Point> outlines;

        Shape shape;

        ShapeDetection detector;
        public Form1()
        {
            InitializeComponent();
            edgeDetection.WorkerReportsProgress = true;
            shapeDetection.WorkerReportsProgress = true;
            shape = Shape.Unknown;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            original = new Bitmap("photo1.png");
            image = new Bitmap("photo1.png");
            shapes = new Bitmap(image.Width, image.Height);
            pictureBox1.Image = (Image)original;
        }

        private void findLines_Click(object sender, EventArgs e)
        {
            if (edgeDetection.IsBusy != true)
            {
                lblStatus.Text = "Finding Edges";
                edgeDetection.RunWorkerAsync();
            }
        }

        private void justTheOutlines(Bitmap image, List<Point> pixels, BackgroundWorker worker)
        {
            lock (image)
            {
                for (int i = 0; i < pixels.Count; i++)
                {
                    image.SetPixel(pixels[i].X, pixels[i].Y, Color.Red);
                    worker.ReportProgress((int)((float)i * 100 / (float)pixels.Count));
                }
            }
        }

        private List<Point> outlineLines(Bitmap image, BackgroundWorker worker)
        {
            int w = image.Width;
            int h = image.Height;

            int alpha = 800000;

            List<Point> changes = new List<Point>();

            lock (image)
            {

                for (int i = 0; i < w; i++)
                {
                    for (int j = 0; j < h; j++)
                    {
                        Color selected = image.GetPixel(i, j);
                        Color nextRight = selected;
                        Color nextDown = selected;

                        if (i < w - 1)
                            nextRight = image.GetPixel(i + 1, j);

                        if (j < h - 1)
                            nextDown = image.GetPixel(i, j + 1);

                        int iSelected = selected.ToArgb();
                        int iNextRight = nextRight.ToArgb();
                        int iNextDown = nextDown.ToArgb();

                        if (Math.Abs(iSelected - iNextRight) > alpha)
                        {
                            if (iSelected < iNextRight)
                            {
                                Point p = new Point(i, j);
                                if(!ContainsPoint(changes, p)) changes.Add(p);
                            }
                            else
                            {
                                Point p = new Point(i + 1, j);
                                if (!ContainsPoint(changes, p)) changes.Add(p);
                            }
                        }

                        if (Math.Abs(iSelected - iNextDown) > alpha)
                        {
                            if (iSelected < iNextDown)
                            {
                                Point p = new Point(i, j);
                                if (!ContainsPoint(changes, p)) changes.Add(p);
                            }
                            else
                            {
                                Point p = new Point(i, j + 1);
                                if (!ContainsPoint(changes, p)) changes.Add(p);
                            }
                        }

                        image.SetPixel(i, j, Color.White);
                    }
                    worker.ReportProgress((int)(((float)i / (float)w) * 100));
                }
            }

            return changes;
        }

        private bool ContainsPoint(List<Point> changes, Point p)
        {
            foreach (Point n in changes)
            {
                if (n.Equals(p)) return true;
            }
            return false;
        }

        private void edgeDetection_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            outlines = outlineLines(image, worker);
            justTheOutlines(image, outlines, worker);
            pictureBox2.Image = (Image)image;
            Thread.Sleep(100);
            image.Save("photo-lines.jpg");
        }

        private void edgeDetection_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            algorithmProgress.Value = e.ProgressPercentage;
        }

        private void edgeDetection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            algorithmProgress.Value = 0;
            findLines.Enabled = false;
            determineShape.Enabled = true;
            lblStatus.Text = "";
        }

        private void determineShape_Click(object sender, EventArgs e)
        {
            if (shapeDetection.IsBusy != true)
            {
                pictureBox1.Image = (Image)image;
                lblStatus.Text = "Running Shape Detection:  Circle -> Quadrilateral";
                shapeDetection.RunWorkerAsync();
            }
        }

        private void ShapeDetection_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            detector = new ShapeDetection(outlines, 40, 10);
            detector.Worker = worker;
            detector.circleChange += new ShapeDetection.CircleChangeEventHandler(circleChange);
            if (detector.IsCircle())
            {
                MessageBox.Show("Object is a circle");
                shape = Shape.Circle;
            }
            else if (detector.IsQuadrilateral())
            {
                MessageBox.Show("Object is a quadrilateral", "Number of edges:  " + detector.Summits);
                shape = Shape.Quadrilateral;
            }
            else
            {
                int sides = detector.Summits.Count;
                if (sides == 3)
                { 
                    MessageBox.Show("Object is a triangle");
                    shape = Shape.Triangle;
                }
                else
                {
                    MessageBox.Show("Number of edges:  " + detector.Summits.Count, "Unknown");
                }
            }

            BitmapDrawing.DrawLines(detector.Summits, shapes);
            BitmapDrawing.DrawSummits(detector.Summits, shapes);
            pictureBox2.Image = (Image)shapes;
            Thread.Sleep(100);
        }

        private void ShapeDetection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (detector != null)
            {
                lblSummits.Text += detector.Summits.Count;
                lblType.Text += shape.ToString();
                determineShape.Enabled = false;
                lblStatus.Text = "";
            }
        }

        void circleChange(object sender, CircleChangeEventArgs e)
        {
            lock (shapes)
            {
                Point p = detector.visited[detector.visited.Count - 1];
                shapes.SetPixel(p.X, p.Y, Color.Blue);
                pictureBox2.Image = (Image)shapes;
                Thread.Sleep(10);
                detector.Worker.ReportProgress((int)(e.percent * 100));
            }
        }

        private void shapeDetection_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            algorithmProgress.Value = e.ProgressPercentage;
        }
    }
}

Update

What Nick said before worked fine. I added that to my CircleChange event and it works. Can somebody explain why the invoke makes it work instead of setting the picturebox2.Image to the shapes image? I mean I call it after I call setpixel, so I 开发者_如何学Goshould be done modifying the image right?

void circleChange(object sender, CircleChangeEventArgs e)
{
    Point p = detector.visited[detector.visited.Count - 1];
    shapes.SetPixel(p.X, p.Y, Color.Blue);
    Image copyForPictureBox = shapes.Clone() as Image;
    BeginInvoke(new Action(() => pictureBox2.Image = copyForPictureBox));
    Thread.Sleep(15);
    detector.Worker.ReportProgress((int)(e.percent * 100));
}


It appears that you're operating on the shapes bitmap on a thread separate to the GUI thread. When you move the window the OnPaint routine will run which will also access the image.

What you need to do to solve this is operate on a separate bitmap in your worker thread, and then pass a copy of that to the GUI thread using Invoke on the form. That way you're guaranteed only to have one thread accessing the picture box image at a time.

Edit:

void MyThreadFunction( )
{
    Bitmap localThreadImage;
    ...

    Image copyForPictureBox = localThreadImage.Clone( ) as Image;

    BeginInvoke( new Action( () => pictureBox.Image = copyForPictureBox ) );

    ....
}

So the idea is that you create a Bitmap on the thread which is only ever accessed by that thread (i.e. your background worker thread). And when you get to a point when you want to update the image in the PictureBox you invoke onto the the GUI thread using BeginInvoke (which doesn't block your worker thread) passing a copy of the Bitmap to the PictureBox.


Locking the shapes object at only one point in your application does not accomplish anything. You also use this bitmap to draw to the window, and my guess is that you are not locking it for drawing. You can either lock it in OnPaint as well, or use a different bitmap for manipulation and display.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜