代码之家  ›  专栏  ›  技术社区  ›  Ed Swangren

如何使用多个线程处理分区中的图像?

  •  0
  • Ed Swangren  · 技术社区  · 17 年前

    我正在尝试快速处理大型图像(约2000x2000)。我正在使用轨迹栏更改图像的整体亮度。问题是轨迹栏(大家都知道)可以很快移动。这是确定的,因为我不需要处理的图像 每一个 轨迹栏的勾号,但它确实需要合理的响应。想象一下图像编辑器中的亮度轨迹栏。

    我尝试过在另一个线程中进行所有处理,这是可行的,但仍然太慢(即使在位图上使用锁位)。我不能使用ColorMatrix,因为它允许组件溢出,我需要在255处剪裁值。因此,我认为通过使用ThreadPool并将图像分割为多个部分,可以获得良好的性能。

    这是我目前掌握的代码。我知道这远非好事,但我只是想解决它,并在这一点上尝试了许多不同的事情。基本概念是使用基础图像作为源,对每个像素执行一些操作,然后将处理后的像素绘制到显示位图中。任何帮助都会很棒。

        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 回复  |  直到 17 年前
        1
  •  4
  •   geofftnz    17 年前

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

        2
  •  1
  •   Reed Copsey    17 年前

    如果你想使用你目前的方法,有几个小的调整,你可以使摆脱比赛条件。

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

    但是,根据您的发布时间框架,您可能需要考虑使用任务并行库而不是线程池。C#4将包括这一点,但如果你不能等那么久,Mono版本在今天运行得相当好。它实际上使这类工作比通过ThreadPool进行要容易得多。

        3
  •  0
  •   mloskot    16 年前

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

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

    一些手术 属于这类算法。