如何使用多个线程分段处理图像?

时间:2009-03-13 00:01:09

标签: c# performance multithreading image-processing

我正在尝试快速处理大图像(约2000x2000)。我正在使用TrackBar来改变图像的整体亮度。问题是TrackBar(众所周知)可以非常快速地移动。这没关系,因为我不需要为TrackBar的每个刻度处理图像,但它确实需要合理响应。想想图像编辑器中的TrackBars亮度。

我已经尝试在另一个线程中进行所有处理,但是它仍然太慢(甚至在Bitmap上使用LockBits)。我不能使用ColorMatrix,因为它允许组件溢出,我需要在255处剪切值。所以,我认为我可以通过使用ThreadPool并将图像分成几个部分来获得良好的性能。

这种方法的问题在于我对多线程应用程序没有多少经验,我不知道如何避免突然出现的竞争条件(在已锁定的图像上调用LockBits等) 。谁能给我一个如何做到这一点的例子?

这是我到目前为止的代码。我知道这远远不是好事,但我现在正在努力解决一些不同的问题。基本概念是使用基础图像作为源,对每个像素执行一些操作,然后将处理后的像素绘制到显示位图中。任何帮助都会很棒。

    public Form1( )
    {
        InitializeComponent( );
        testPBox1.Image = Properties.Resources.test;
        trackBar1.Value = 100;
        trackBar1.ValueChanged += trackBar1_ValueChanged;        
    }     

    void trackBar1_ValueChanged( object sender, EventArgs e )
    {
        testPBox1.IntensityScale = (float) trackBar1.Value / 100;
    }

class TestPBox : Control
{
    private const int MIN_SAT_WARNING = 240;        
    private Bitmap m_srcBitmap;
    private Bitmap m_dispBitmap;
    private float m_scale;           

    public TestPBox( )
    {
        this.DoubleBuffered = true;                                              
        IntensityScale = 1.0f;
    }      

    public Bitmap Image
    {
        get
        {
            return m_dispBitmap;
        }
        set
        {
            if ( value != null )
            {
                m_srcBitmap = value;
                m_dispBitmap = (Bitmap) value.Clone( );
                Invalidate( );
            }
        }
    }

    [DefaultValue( 1.0f )]
    public float IntensityScale
    {
        get
        {
            return m_scale;
        }
        set
        {
            if ( value == 0.0 || m_scale == value )
            {                   
                return;
            }

            if ( !this.DesignMode )
            {
                m_scale = value;
                ProcessImage( );                    
            }
        }
    }   

    private void ProcessImage( )
    {
        if ( Image != null )
        {
            int sections = 10;
            int sectionHeight = (int) m_srcBitmap.Height / sections;
            for ( int i = 0; i < sections; ++i )
            {
                Rectangle next = new Rectangle( 0, i * sectionHeight, m_srcBitmap.Width, sectionHeight );
                ThreadPool.QueueUserWorkItem( new WaitCallback( delegate { ChangeIntensity( next ); } ) );
            }
        }
    }

    private unsafe void ChangeIntensity( Rectangle rect )
    {            
        BitmapData srcData = m_srcBitmap.LockBits( rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb );
        BitmapData dspData = m_dispBitmap.LockBits( rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb );            
        byte* pSrc = (byte*) srcData.Scan0;
        byte* pDsp = (byte*) dspData.Scan0;

        for ( int y = 0; y < rect.Height; ++y )
        {
            for ( int x = 0; x < rect.Width; ++x )
            {
                // we are dealing with a monochrome image, so r = g = b.
                // We only need to get one component value to use for all r, g, and b.
                byte b = (byte) CompMinMax( 0, 255, (int) ( pSrc[0] * m_scale ) );
                Color c = ( b > MIN_SAT_WARNING ) ? Color.FromArgb( b, Color.Red ) : Color.FromArgb( 255, b, b, b );                     

                // windows stores images internally in 
                // reverse byte order, i.e., Bgra, not Argb.
                pDsp[3] = (byte) c.A;
                pDsp[2] = (byte) c.R;
                pDsp[1] = (byte) c.G;
                pDsp[0] = (byte) c.B;

                pSrc += 4;
                pDsp += 4;                        
            }
        }

        m_srcBitmap.UnlockBits( srcData );
        m_dispBitmap.UnlockBits( dspData );
        this.Invalidate( );                      
    }

    private int CompMinMax( int min, int max, int value )
    {
        if ( value > max ) return max;
        if ( value < min ) return min;
        return value;
    }        

    protected override void OnPaint( PaintEventArgs e )
    {
        if ( Image != null )
        {               
            Graphics g = e.Graphics;
            Rectangle drawingRect = PaintUtils.CenterInRect( ClientRectangle, PaintUtils.ScaleRect( ClientRectangle, Image.Size ).Size );
            g.DrawImage( Image, drawingRect, 0, 0, Image.Width, Image.Height, GraphicsUnit.Pixel );         
        }

        base.OnPaint( e );
    }
}

3 个答案:

答案 0 :(得分:4)

您是否考虑过生成图像的缩小版本并在用户拖动轨迹栏时对其进行图像处理?这将为您提供响应能力,同时仍然向用户提供有用的反馈,直到他们确定最终值。

答案 1 :(得分:1)

如果您想使用当前的方法,可以通过一些小的调整来摆脱竞争条件。

您需要更改的是调整它以便在ProcessImage中调用LockBits / UnlockBits,而不是在单个线程池请求中调用。但是,您还需要跟踪传递给线程池委托的WaitHandle,以便阻止所有线程池线程完成。只是不要解锁这些位,使其无效,并在所有代表完成之后返回。

但是,根据您的发布时间表,您可能需要考虑使用任务并行库而不是ThreadPool。 C#4将包括这个,但是如果你不能等那么久,Mono版本今天工作得相当好。它实际上使这种类型的工作比通过ThreadPool更容易。

答案 2 :(得分:0)

通常,对图像子集进行多线程处理是可能的,但通常通过处理子集和处理整个图像可以产生相同结果的算法是有意义的。换句话说,如果A和B是图像的子集,那么这应该是有效的等式:

alg(A + B) = alg(A) + alg(B)

因此,您需要确定某些操作是否属于此类算法。