在 C# 中使用多线程屏蔽/过滤图像(Windows 窗体应用程序)

Posted

技术标签:

【中文标题】在 C# 中使用多线程屏蔽/过滤图像(Windows 窗体应用程序)【英文标题】:Using Multithreading to mask/filter a image in C# (windows form application) 【发布时间】:2018-10-16 06:19:36 【问题描述】:

我目前正在尝试学习如何在 C# 中使用多线程,但我真的很难让用户从 UI 中选择要使用的线程数。

在一些来源的帮助下,我编写了代码以使用中值滤波器屏蔽图像,现在想应用多线程 (1-64)。

我知道线程是如何工作的,但我正在努力寻找在这个程序中实现它的方法。

有人可以帮助我理解吗,不要向我展示所需的代码,而是解释应该在哪里实现线程以及如何允许用户选择线程数量。

代码

类 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 类 ---------------------------------------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);
        
    

【问题讨论】:

【参考方案1】:

我们可以使用Parallel.For 方法为我们进行并行化。

我们必须让所有在并行循环中被修改的变量都是本地的。

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];
    );
;

这使我的机器的性能提高了两倍以上(2 个物理内核,4 个逻辑内核)。


外循环的并行化肯定会带来更多的加速。但是,让它变得更加困难。

以类似的方式,可以对灰度滤镜进行并行化。

【讨论】:

以上是关于在 C# 中使用多线程屏蔽/过滤图像(Windows 窗体应用程序)的主要内容,如果未能解决你的问题,请参考以下文章

c#中利用system.timers多线程做图像处理,图像保存时提示“GDI+ 中发生一般性错误”,如何解决?

Control类的Invoke 和 BeginInvoke

Control类的Invoke 和 BeginInvoke

有效地将图像文件保存到磁盘 c#

C#多线程のSemaphore(信号量,负责协调各个线程)

如何在 C# 中调用 Sobel 过滤器、图像处理的函数