简体   繁体   中英

Using Multithreading to mask/filter a image in C# (windows form application)

I'm currently trying to learn how to use multithreading in C# and I'm really struggling with letting the user choose how many threads to use from a UI.

I have, with the help of a few sources, written the code to mask a image using the median filter and now want to apply multithreading (1-64).

I know how threads work, but I'm struggling to find a way to implement it in this program.

Can someone please help me understand by not showing me the code needed, but explaining where threading should be implemented and how to allow the user to choose the amount of threads.

Code

class ExtBitmap

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing.Imaging;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Drawing2D;

namespace EilandFotoPRAK1
{
    public static class ExtBitmap
    {
        public static Bitmap CopyToSquareCanvas(this Bitmap sourceBitmap, int canvasWidthLenght)
        {
            float ratio = 1.0f;
            int maxSide = sourceBitmap.Width > sourceBitmap.Height ?
                          sourceBitmap.Width : sourceBitmap.Height;

            ratio = (float)maxSide / (float)canvasWidthLenght;

            Bitmap bitmapResult = (sourceBitmap.Width > sourceBitmap.Height ?
                                    new Bitmap(canvasWidthLenght, (int)(sourceBitmap.Height / ratio))
                                    : new Bitmap((int)(sourceBitmap.Width / ratio), canvasWidthLenght));

            using (Graphics graphicsResult = Graphics.FromImage(bitmapResult))
            {
                graphicsResult.CompositingQuality = CompositingQuality.HighQuality;
                graphicsResult.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphicsResult.PixelOffsetMode = PixelOffsetMode.HighQuality;

                graphicsResult.DrawImage(sourceBitmap,
                                        new Rectangle(0, 0,
                                            bitmapResult.Width, bitmapResult.Height),
                                        new Rectangle(0, 0,
                                            sourceBitmap.Width, sourceBitmap.Height),
                                            GraphicsUnit.Pixel);
                graphicsResult.Flush();
            }

            return bitmapResult;
        }

        public static Bitmap MedianFilter(this Bitmap sourceBitmap,
                                                int matrixSize,
                                                  int bias = 0,
                                         bool grayscale = false)
        {
            BitmapData sourceData =
                       sourceBitmap.LockBits(new Rectangle(0, 0,
                       sourceBitmap.Width, sourceBitmap.Height),
                       ImageLockMode.ReadOnly,
                       PixelFormat.Format32bppArgb);

            byte[] pixelBuffer = new byte[sourceData.Stride *
                                          sourceData.Height];

            byte[] resultBuffer = new byte[sourceData.Stride *
                                           sourceData.Height];

            Marshal.Copy(sourceData.Scan0, pixelBuffer, 0,
                                       pixelBuffer.Length);

            sourceBitmap.UnlockBits(sourceData);

            if (grayscale == true)
            {
                float rgb = 0;

                for (int k = 0; k < pixelBuffer.Length; k += 4)
                {
                    rgb = pixelBuffer[k] * 0.11f;
                    rgb += pixelBuffer[k + 1] * 0.59f;
                    rgb += pixelBuffer[k + 2] * 0.3f;


                    pixelBuffer[k] = (byte)rgb;
                    pixelBuffer[k + 1] = pixelBuffer[k];
                    pixelBuffer[k + 2] = pixelBuffer[k];
                    pixelBuffer[k + 3] = 255;
                }
            }

            int filterOffset = (matrixSize - 1) / 2;
            int calcOffset = 0;

            int byteOffset = 0;

            List<int> neighbourPixels = new List<int>();
            byte[] middlePixel;

            for (int offsetY = filterOffset; offsetY <
                sourceBitmap.Height - filterOffset; offsetY++)
            {
                for (int offsetX = filterOffset; offsetX <
                    sourceBitmap.Width - filterOffset; offsetX++)
                {
                    byteOffset = offsetY *
                                 sourceData.Stride +
                                 offsetX * 4;

                    neighbourPixels.Clear();

                    for (int filterY = -filterOffset;
                        filterY <= filterOffset; filterY++)
                    {
                        for (int filterX = -filterOffset;
                            filterX <= filterOffset; filterX++)
                        {

                            calcOffset = byteOffset +
                                         (filterX * 4) +
                                         (filterY * sourceData.Stride);

                            neighbourPixels.Add(BitConverter.ToInt32(
                                             pixelBuffer, calcOffset));
                        }
                    }

                    neighbourPixels.Sort();

                    middlePixel = BitConverter.GetBytes(
                                       neighbourPixels[filterOffset]);

                    resultBuffer[byteOffset] = middlePixel[0];
                    resultBuffer[byteOffset + 1] = middlePixel[1];
                    resultBuffer[byteOffset + 2] = middlePixel[2];
                    resultBuffer[byteOffset + 3] = middlePixel[3];
                }
            }

            Bitmap resultBitmap = new Bitmap(sourceBitmap.Width,
                                             sourceBitmap.Height);

            BitmapData resultData =
                       resultBitmap.LockBits(new Rectangle(0, 0,
                       resultBitmap.Width, resultBitmap.Height),
                       ImageLockMode.WriteOnly,
                       PixelFormat.Format32bppArgb);

            Marshal.Copy(resultBuffer, 0, resultData.Scan0,
                                       resultBuffer.Length);

            resultBitmap.UnlockBits(resultData);

            return resultBitmap;
        }
    }
}

FrmPhoto class --------------------------------------- How the GUI Looks

public partial class FrmPhoto : Form
    {
        private Bitmap originalBitmap = null;
        private Bitmap previewBitmap = null;
        private Bitmap resultBitmap = null;

        public FrmPhoto()
        {
            InitializeComponent();

            cmbEdge.SelectedIndex = 0;
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Title = "Select an image file.";
            dialog.Filter = "Png Images(*.png)|*.png|Jpeg Images(*.jpg)|*.jpg";
            dialog.Filter += "|Bitmap Images(*.bmp)|*.bmp";


            if (dialog.ShowDialog() == DialogResult.OK)
            {
                StreamReader streamReader = new StreamReader(dialog.FileName);
                originalBitmap = (Bitmap)Bitmap.FromStream(streamReader.BaseStream);
                streamReader.Close();

                previewBitmap = originalBitmap.CopyToSquareCanvas(pix1.Width);
                pix1.Image = previewBitmap;
            }
        }

        private void ApplyFilter(bool preview)
        {
            if (previewBitmap == null || cmbEdge.SelectedIndex == -1)
            {
                return;
            }

            Bitmap selectedSource = null;
            Bitmap bitmapResult = null;

            if (preview == true)
            {
                selectedSource = previewBitmap;
            }
            else
            {
                selectedSource = originalBitmap;
            }

            if (selectedSource != null)
            {
                if (cmbEdge.SelectedItem.ToString() == "None")
                {
                    bitmapResult = selectedSource;
                }
                else if (cmbEdge.SelectedItem.ToString() == "Median 3x3")
                {
                    bitmapResult = selectedSource.MedianFilter(3);
                }
                else if (cmbEdge.SelectedItem.ToString() == "Median 5x5")
                {
                    bitmapResult = selectedSource.MedianFilter(5);
                }
                else if (cmbEdge.SelectedItem.ToString() == "Median 7x7")
                {
                    bitmapResult = selectedSource.MedianFilter(7);
                }
                else if (cmbEdge.SelectedItem.ToString() == "Median 9x9")
                {
                    bitmapResult = selectedSource.MedianFilter(9);
                }
                else if (cmbEdge.SelectedItem.ToString() == "Median 11x11")
                {
                    bitmapResult = selectedSource.MedianFilter(11);
                }
                else if (cmbEdge.SelectedItem.ToString() == "Median 13x13")
                {
                    bitmapResult = selectedSource.MedianFilter(13);
                }
            }

            if (bitmapResult != null)
            {
                if (preview == true)
                {
                    pix1.Image = bitmapResult;
                }
                else
                {
                    resultBitmap = bitmapResult;
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            ApplyFilter(true);
        }
    }

We can use Parallel.For method that will make parallelization for us.

We have to make all variables that are modified inside the parallel loop are local.

int filterOffset = (matrixSize - 1) / 2;
//int calcOffset = 0;
//int byteOffset = 0;
//List<int> neighbourPixels = new List<int>();
//byte[] middlePixel;

var w = sourceBitmap.Width - filterOffset;
for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++)
{
    Parallel.For(filterOffset, w, offsetX =>
    {
        var byteOffset = offsetY * sourceData.Stride + offsetX * 4; // local

        var neighbourPixels = new List<int>(); // local
        //neighbourPixels.Clear();

        for (int filterY = -filterOffset; filterY <= filterOffset; filterY++)
        {
            for (int filterX = -filterOffset; filterX <= filterOffset; filterX++)
            {
                var calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride); // local
                neighbourPixels.Add(BitConverter.ToInt32(pixelBuffer, calcOffset));
            }
        }

        neighbourPixels.Sort();
        var middlePixel = BitConverter.GetBytes(neighbourPixels[filterOffset]); // local

        resultBuffer[byteOffset] = middlePixel[0];
        resultBuffer[byteOffset + 1] = middlePixel[1];
        resultBuffer[byteOffset + 2] = middlePixel[2];
        resultBuffer[byteOffset + 3] = middlePixel[3];
    });
};

This gives more than a two time increase in performance on my machine (2 physical cores, 4 logical).


Parallelization of the outer loop would certainly give more acceleration. However, to make it much more difficult.

In a similar way it is possible to make a parallelization of the grayscale filter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM