2010-06-25 130 views
6

¿Hay alguna forma eficiente de ajustar el contraste de una imagen en C#?Ajuste el contraste de una imagen en C# eficientemente

He visto this article que recomienda realizar una operación por píxel. No rápido

Estoy usando matrices de color en lugares ya y las encuentro rápidas. ¿Hay alguna forma de ajustar el contraste? (Nota: This guy se equivoca.)

También estoy usando EmguCV. Observé que OpenCV (que Emgu envuelve) seems to have a contrast function - ¿hay alguna forma de acceder a esto a través de Emgu? Por el momento, todo lo que puedo hacer en Emgu es normalizar el histograma, que cambia el contraste, pero no con ningún grado de control de mi parte.

¿Alguien tiene alguna idea?

+0

¿Por qué dice que " ese tipo "se equivoca? – DkAngelito

+0

@DkAngelito Wow, esto es de hace mucho tiempo. Si la memoria sirve, el enfoque de ColorMatrix no puede desplazar los valores lejos/hacia el punto medio, que es lo que realmente es un ajuste de contraste. Si ayuda, la respuesta aceptada por MusicGenesis a continuación parece haber atraído el consenso como óptimo. –

+0

El enlace "Este chico" ahora está roto. – Chad

Respuesta

26

Si el código en esa muestra funciona para usted, puede acelerarlo masivamente (en órdenes de magnitud) utilizando Bitmap.LockBits, que devuelve un objeto BitmapData que permite el acceso a los datos de píxeles del mapa de bits mediante punteros. Existen numerosas muestras en la web y en StackOverflow que muestran cómo usar LockBits.

Bitmap.SetPixel() y Bitmap.GetPixel() son los métodos más lentos conocidos por la humanidad, y ambos utilizan la clase Color, que es la clase más lenta conocida por la humanidad. Deberían haber sido nombrados Bitmap.GetPixelAndByGodYoullBeSorryYouDid() y Bitmap.SetPixelWhileGettingCoffee como una advertencia para los desarrolladores incautos.

Actualización: Si va a modificar el código en esa muestra, cabe destacar que este trozo:

System.Drawing.Bitmap TempBitmap = Image; 
System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(TempBitmap.Width, 
    TempBitmap.Height); 
System.Drawing.Graphics NewGraphics = 
    System.Drawing.Graphics.FromImage(NewBitmap); 
NewGraphics.DrawImage(TempBitmap, new System.Drawing.Rectangle(0, 0, 
    TempBitmap.Width, TempBitmap.Height), 
    new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), 
    System.Drawing.GraphicsUnit.Pixel); 
NewGraphics.Dispose(); 

puede ser sustituida por la siguiente:

Bitmap NewBitmap = (Bitmap)Image.Clone(); 

Actualización 2: Aquí está la versión de LockBits del método AdjustContrast (con algunas otras mejoras de velocidad):

public static Bitmap AdjustContrast(Bitmap Image, float Value) 
{ 
    Value = (100.0f + Value)/100.0f; 
    Value *= Value; 
    Bitmap NewBitmap = (Bitmap)Image.Clone(); 
    BitmapData data = NewBitmap.LockBits(
     new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), 
     ImageLockMode.ReadWrite, 
     NewBitmap.PixelFormat); 
    int Height = NewBitmap.Height; 
    int Width = NewBitmap.Width; 

    unsafe 
    { 
     for (int y = 0; y < Height; ++y) 
     { 
      byte* row = (byte*)data.Scan0 + (y * data.Stride); 
      int columnOffset = 0; 
      for (int x = 0; x < Width; ++x) 
      { 
       byte B = row[columnOffset]; 
       byte G = row[columnOffset + 1]; 
       byte R = row[columnOffset + 2]; 

       float Red = R/255.0f; 
       float Green = G/255.0f; 
       float Blue = B/255.0f; 
       Red = (((Red - 0.5f) * Value) + 0.5f) * 255.0f; 
       Green = (((Green - 0.5f) * Value) + 0.5f) * 255.0f; 
       Blue = (((Blue - 0.5f) * Value) + 0.5f) * 255.0f; 

       int iR = (int)Red; 
       iR = iR > 255 ? 255 : iR; 
       iR = iR < 0 ? 0 : iR; 
       int iG = (int)Green; 
       iG = iG > 255 ? 255 : iG; 
       iG = iG < 0 ? 0 : iG; 
       int iB = (int)Blue; 
       iB = iB > 255 ? 255 : iB; 
       iB = iB < 0 ? 0 : iB; 

       row[columnOffset] = (byte)iB; 
       row[columnOffset + 1] = (byte)iG; 
       row[columnOffset + 2] = (byte)iR; 

       columnOffset += 4; 
      } 
     } 
    } 

    NewBitmap.UnlockBits(data); 

    return NewBitmap; 
} 

NOTA: este código requiere using System.Drawing.Imaging; en las instrucciones de uso de su clase, y requiere que se verifique la opción allow unsafe code del proyecto (en la pestaña Propiedades de compilación del proyecto).

Uno de los motivos por los que GetPixel y SetPixel son tan lentos para las operaciones de píxel por píxel es que la sobrecarga de la llamada al método en sí misma comienza a ser un factor importante. Normalmente, mi ejemplo de código aquí sería considerado un candidato para la refactorización, ya que podría escribir sus propios métodos SetPixel y GetPixel que usan un objeto BitmapData existente, pero el tiempo de procesamiento para los cálculos dentro de las funciones sería muy pequeño en relación con el método de cada llamada. Es por eso que eliminé también las llamadas Clamp en el método original.

Otra forma de acelerar esto sería simplemente convertirla en una función "destructiva" y modificar el parámetro Bitmap pasado en lugar de hacer una copia y devolver la copia modificada.

+2

Supongo que estás a cargo de nombrar cosas donde trabajas? –

+0

Correcto, como * I * tengo un trabajo. :) – MusiGenesis

+0

+1 bravo

2

@MusiGenesis,

Sólo quería señalar que he utilizado este método para un editor de imágenes que he estado escribiendo.Funciona bien, pero a veces este método provoca una AccessViolationException en esta línea:

byte B = row[columnOffset]; 

me di cuenta que era porque no había un estándar de bitdepth, por lo que si una imagen fue de 32 bits de color que estaba recibiendo este error. Así que cambié esta línea:

BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadWrite, NewBitmap.PixelFormat); 

a:

BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb); 

Espero que esto ayude, ya que parece haber erradicado mi problema.

Gracias por el mensaje.

foque

+1

Puede usar 'PixelFormat.Format32bppArgb', ya que ese es el formato predeterminado de mapa de bits .NET. – MusiGenesis

-1

Estoy un poco tarde, pero el uso de una implementación de matriz de color ya que estos serán optimizados para tales transformaciones y es mucho más fácil que la manipulación de los píxeles a sí mismo: http://www.geekpedia.com/tutorial202_Using-the-ColorMatrix-in-Csharp.html

+1

Este es el mismo error que Bob comete en el artículo al que me refiero en mi pregunta: las matrices de colores no son adecuadas para las alteraciones de contraste. Esto se debe a que los cambios se realizan en relación con el gris medio, no negro o blanco. –

+0

Solo para corregir esto, 'nuevo float [] {0.1f, 0, 0, 0, 0}, nuevo float [] {0, 0.1f, 0, 0, 0}, nuevo float [] {0 , 0, 0.1f, 0, 0}, nuevo float [] {0, 0, 0, 1, 0}, nuevo float [] {0.8f, 0.8f, 0.8f, 0, 1} ' Does el trabajo en el ejemplo anterior. La quinta fila agrega un desplazamiento que se puede ajustar. Esto (0.8) da un efecto de "alta clave". 0.2 es muy bajo contraste. – nick66

Cuestiones relacionadas