Como acelerar meu limite de cores em C#
-
09-12-2019 - |
Pergunta
Estou trabalhando no rastreamento de objetos com base em cores e estava usando a biblioteca EmguCV para limitar minha imagem colorida a uma imagem binária em preto e branco.O limite em si foi bastante rápido, 50ms em imagem 320x240.Estou usando o espaço de cores RG Chromaticity, então há alguns cálculos necessariamente.
Agora estou tentando acelerar usando ponteiros, mas o resultado é muito parecido com o que fiz com o emguCV (cerca de 50ms por imagem).
Quero perguntar, se há algum especialista que possa me ajudar, o que estou fazendo de errado.Aqui está meu pequeno trecho de código da minha implementação de limite de cores.É baseado neste http://www.bobpowell.net/onebit.htm.
public static Bitmap ThresholdRGChroma(Bitmap original, double angleMin,
double angleMax, double satMin, double satMax)
{
Bitmap bimg = new Bitmap(original.Width, original.Height, PixelFormat.Format1bppIndexed);
BitmapData imgData = original.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadOnly, original.PixelFormat);
BitmapData bimgData = bimg.LockBits(new Rectangle(0, 0, bimg.Width, bimg.Height), ImageLockMode.ReadWrite, bimg.PixelFormat);
int pixelSize = 3;
double r, g, angle, sat;
unsafe
{
byte* R, G, B;
byte* row;
int RGBSum;
for (int y = original.Height - 1; y >= 0; y--)
{
row = (byte*)imgData.Scan0 + (y * imgData.Stride);
for (int x = original.Width - 1; x >= 0; x--)
{
// get rgb values
B = &row[x * pixelSize];
G = &row[x * pixelSize + 1];
R = &row[x * pixelSize + 2];
RGBSum = *R + *G + *B;
if (RGBSum == 0)
{
SetIndexedPixel(x, y, bimgData, false);
continue;
}
//calculate r ang g for rg chroma color space
r = (double)*R / RGBSum;
g = (double)*G / RGBSum;
//and angle and saturation
angle = GetAngleRad(r, g) * (180.0 / Math.PI);
sat = Math.Sqrt(Math.Pow(g, 2) + Math.Pow(r, 2));
//conditions to set pixel black or white
if ((angle >= angleMin && angle <= angleMax) && (sat >= satMin && sat <= satMax))
SetIndexedPixel(x, y, bimgData, true);
else
SetIndexedPixel(x, y, bimgData, false);
}
}
}
bimg.UnlockBits(bimgData);
original.UnlockBits(imgData);
return bimg;
}
private unsafe static void SetIndexedPixel(int x, int y, BitmapData bmd, bool pixel)
{
int index = y * bmd.Stride + (x >> 3);
byte* p = (byte*)bmd.Scan0.ToPointer();
byte mask = (byte)(0x80 >> (x & 0x7));
if (pixel)
p[index] |= mask;
else
p[index] &= (byte)(mask ^ 0xff);
}
private static double GetAngleRad(double x, double y)
{
if (x - _rgChromaOriginX == 0)
return 0.0;
double angle = Math.Atan((y - _rgChromaOriginY) / (x - _rgChromaOriginX)); // 10ms
if (x < _rgChromaOriginX && y > _rgChromaOriginY)
angle = angle + Math.PI;
else if (x < _rgChromaOriginX && y < _rgChromaOriginY)
angle = angle + Math.PI;
else if (x > _rgChromaOriginX && y < _rgChromaOriginY)
angle = angle + 2 * Math.PI;
return angle;
}
Solução
Você está fazendo muitas contas desnecessárias para cada pixel, calculando valores exatos apenas para verificar se eles estão dentro de alguns limites.Você pode simplificar as comparações pré-computando alguns ajustes nos limites.
A substituição mais fácil é pela saturação.Você está calculando uma raiz quadrada que pode ser evitada elevando ao quadrado os limites.
double satMin2 = satMin*satMin;
double satMax2 = satMax*satMax;
// ...
sat2 = g*g + r*r;
//conditions to set pixel black or white
if ((angle >= angleMin && angle <= angleMax) && (sat2 >= satMin2 && sat <= satMax2))
Um truque semelhante pode ser usado com o ângulo.Em vez de calcular o ângulo com Math.Atan, descubra a que esses limites equivalem em seus intervalos r e g.
Outras dicas
Não tenho certeza se será mais rápido.Mas aqui Escrevi uma dica sobre como trabalhar com bitmaps muito rápido.
Para completar, aqui está a versão modificada da imagem de limite no espaço de cores RG Chromaticity, que é mais de 2 vezes mais rápida que a versão na minha pergunta.
public static Bitmap ThresholdRGChroma(Bitmap original, Rectangle roi, double angle,
double width, double satMin, double satMax)
{
Bitmap bimg = new Bitmap(original.Width, original.Height, PixelFormat.Format1bppIndexed);
BitmapData imgData = original.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadOnly, original.PixelFormat);
BitmapData bimgData = bimg.LockBits(new Rectangle(0, 0, bimg.Width, bimg.Height), ImageLockMode.ReadWrite, bimg.PixelFormat);
int pixelSize = 3;
double r, g, sat, m;
double satMin2 = satMin * satMin;
double satMax2 = satMax * satMax;
double cr = Math.Sin((2 * Math.PI * angle) / 360.0);
double cg = Math.Cos((2 * Math.PI * angle) / 360.0);
// Instead of (Math.Cos(2 * width / 180.0) + 1) / 2.0 I'm using pre-calculated <1; 0> values.
double w2 = -width;
unsafe
{
byte* R, G, B;
byte* row;
int RGBSum;
for (int y = original.Height - 1; y >= 0; y--)
{
row = (byte*)imgData.Scan0 + (y * imgData.Stride);
for (int x = original.Width - 1; x >= 0; x--)
{
B = &row[x * pixelSize];
G = &row[x * pixelSize + 1];
R = &row[x * pixelSize + 2];
RGBSum = *R + *G + *B;
if (RGBSum == 0)
{
SetIndexedPixel(x, y, bimgData, false);
continue;
}
r = (double)*R / RGBSum - _rgChromaOriginX;
g = (double)*G / RGBSum - _rgChromaOriginY;
m = cr * r + cg * g;
sat = r * r + g * g;
if (m > 0 && m * m > w2 * w2 * sat && sat >= satMin2 && sat <= satMax2)
SetIndexedPixel(x, y, bimgData, true);
else
SetIndexedPixel(x, y, bimgData, false);
}
}
}
bimg.UnlockBits(bimgData);
original.UnlockBits(imgData);
return bimg;
}