从其他图像替换位图图像的彩色像素

问题描述 投票:0回答:3

我遇到性能问题。

对于鞋垫模型配置器,我们要上传一个样片,许多材料图像要与该样片图像融合。

我应将实物图像上的每个白色像素替换为实物图像上的相应像素。

由于实物图像不是单色,所以我不能仅用另一种单色替换所有白色。

图像大小相同。因此,如果颜色对于块图像而言不是透明的,并且在实体图像上具有相同的X和Z坐标,则我只是获取一个像素,就获取一个像素,然后设置块图像的像素。

但是由于材料很多,今天要花5分钟。

是否有一种优化的方法来做到这一点?

这是我的方法:

            //For every material image, calls the fusion method below.
            foreach (string material in System.IO.Directory.GetFiles(materialsPath))
            {
               var result = FillWhiteImages(whiteImagesFolder, whiteImagesFolder + "\\" + System.IO.Path.GetFileName(whiteFilePath), material);

            }


        private static void FusionWhiteImagesWithMaterials(string whiteImageFolder, string file, string materialImageFile)
        {
        if (file.ToLower().EndsWith(".db") || materialImageFile.ToLower().EndsWith(".db"))
            return;


        List<CustomPixel> lstColoredPixels = new List<CustomPixel>();


        try
        {
            Bitmap image = new Bitmap(file);
            for (int y = 0; y < image.Height; ++y)
            {
                for (int x = 0; x < image.Width; ++x)
                {
                    if (image.GetPixel(x, y).A > 0)
                    {
                        lstColoredPixels.Add(new CustomPixel(x, y));
                    }
                }
            }

            Bitmap bmpTemp = new Bitmap(materialImageFile);
            Bitmap target = new Bitmap(bmpTemp, new Size(image.Size.Width, image.Size.Height));

            for (int y = 0; y < target.Height; y++)
            {
                for (int x = 0; x < target.Width; x++)
                {
                    Color clr = image.GetPixel(x, y);
                    if (clr.A > 0)
                    {
                        if (clr.R > 200 && clr.G > 200 && clr.B > 200)
                            image.SetPixel(x, y, target.GetPixel(x, y));
                        else
                            image.SetPixel(x, y, Color.Gray);
                    }
                }
            }

         ... 
         image.Save(...);  
        }
        catch (Exception ex)
        {

        }
    }

// I缩小图像尺寸以保留在屏幕上。实际图像尺寸为500x1240像素。

Sample piece image

Sample material image

c# bitmap
3个回答
1
投票

GetPixel / SetPixel由于锁定和访问像素的其他开销而非常慢。为了提高性能,您将需要使用一些非托管编码来直接访问数据。

This answer should shows an example on how to improve speed when working with bitmaps.

这里有一些(未经测试!)代码改编自该答案:

    public static unsafe Image MergeBitmaps(Bitmap mask, Bitmap background)
    {
        Debug.Assert(mask.PixelFormat == PixelFormat.Format32bppArgb);
        BitmapData maskData = mask.LockBits(new Rectangle(0, 0, mask.Width, mask.Height),
            ImageLockMode.ReadWrite, mask.PixelFormat);
        BitmapData backgroundData = background.LockBits(new Rectangle(0, 0, background.Width, background.Height),
            ImageLockMode.ReadWrite, background.PixelFormat);
        try
        {
            byte bytesPerPixel = 4;

            /*This time we convert the IntPtr to a ptr*/
            byte* maskScan0 = (byte*)maskData.Scan0.ToPointer();
            byte* backgroundScan0 = (byte*)backgroundData.Scan0.ToPointer();
            for (int i = 0; i < maskData.Height; ++i)
            {
                for (int j = 0; j < maskData.Width; ++j)
                {
                    byte* maskPtr = maskScan0 + i * maskData.Stride + j * bytesPerPixel;
                    byte* backPtr = backgroundScan0 + i * backgroundData.Stride + j * bytesPerPixel;

                    //maskPtr is a pointer to the first byte of the 4-byte color data
                    //maskPtr[0] = blueComponent;
                    //maskPtr[1] = greenComponent;
                    //maskPtr[2] = redComponent;
                    //maskPtr[3] = alphaComponent;
                    if (maskPtr[3] > 0 )
                    {
                        if (maskPtr[2] > 200 &&
                            maskPtr[1] > 200 &&
                            maskPtr[0] > 200)
                        {
                            maskPtr[3] = 255;
                            maskPtr[2]  = backPtr[2];
                            maskPtr[1]  = backPtr[1];
                            maskPtr[0]  = backPtr[0];
                        }
                        else
                        {
                            maskPtr[3] = 255;
                            maskPtr[2] = 128;
                            maskPtr[1] = 128;
                            maskPtr[0] = 128;
                        }
                    }
                }
            }
            return mask;
        }
        finally
        {
            mask.UnlockBits(maskData);
            background.UnlockBits(backgroundData);
        }
    }
}

0
投票

我找到了这个解决方案,它要快得多。

但是它使用过多的资源。

使用C#进行并行编程对我有所帮助:

         //I called my method in a parallel foreach 
         Parallel.ForEach(System.IO.Directory.GetFiles(materialsPath), filling =>
            {
               var result = FillWhiteImages(whiteImagesFolder, whiteImagesFolder + "\\" + System.IO.Path.GetFileName(whiteFilePath), filling);
            });





        //Instead of a classic foreach loop like this.
        foreach (string material in System.IO.Directory.GetFiles(materialsPath))
        {
           var result = FillWhiteImages(whiteImagesFolder, whiteImagesFolder + "\\" + System.IO.Path.GetFileName(whiteFilePath), material);

        }

0
投票

替换白色是一种可能,但不是很漂亮。根据那里的图像,理想的解决方案是使用正确的Alpha来获得图案,然后在其上绘制可见的黑线。这实际上是一个包含更多步骤的过程:

  • 从脚形图像中提取Alpha
  • 从脚形图像中提取黑线
  • 将Alpha应用于图案图像
  • 在黑色调整的图案图像上绘制黑线

幸运的是,前三个步骤很容易组合成一个循环。

我处理此问题的方法是将两个图像的数据提取为ARGB字节数组,这意味着每个像素为四个字节,顺序为B,G,R,A。然后,对于每个像素,我们只需复制从脚形图像到样式图像的Alpha字节的alpha字节,因此最终得到图案图像,并对其应用了脚形的透明度。

现在,在相同大小的新字节数组中,该数组以纯00字节开头(意味着,由于A,R,G和B均为零,透明黑色),我们构造了黑线。如果像素不是白色且可见,则可以将其视为“黑色”。因此,理想的结果(包括平滑的淡入淡出)是将新图像的alpha调整为alpha的最小值和亮度的倒数。由于是灰度,因此R,G,B中的任何一个都可以提高亮度。要获取逆值作为字节值,我们只取(255 - brightness)

注意,如果需要将此应用于图像负载,则可能只希望提前提取一次脚部图案图像的字节,尺寸和步幅,并将其保留在变量中以进行填充过程:] >

private void BakeImages(String whiteFilePath, String materialsFolder, String resultFolder)
{
    Int32 width;
    Int32 height;
    Int32 stride;
    // extract bytes of shape & alpha image
    Byte[] shapeImageBytes;
    using (Bitmap picFootshape = new Bitmap(whiteFilePath))
    {
        width = picFootshape.Width;
        height = picFootshape.Height;
        // extract bytes of shape & alpha image
        shapeImageBytes = GetImageData(picFootshape, out stride, PixelFormat.Format32bppArgb);
    }
    //For every material image, calls the fusion method below.
    foreach (string materialImagePath in Directory.GetFiles(materialsFolder))
    {
        using (Bitmap materialImage = new Bitmap(materialImagePath))
        using (Bitmap result = FillWhiteImage(shapeImageBytes, width, height, stride, materialImage))
        {
            if (result == null)
                continue;
            result.Save(Path.Combine(resultFolder, Path.GetFileNameWithoutExtension(materialImagePath) + ".png"), ImageFormat.Png);
        }
    }
}

因此,无论如何,对于实际的图像处理,连同前面的所有步骤,我们都获得此代码:

private Bitmap FillWhiteImage(Byte[] shapeImageBytes, Int32 width, Int32 height, Int32 stride, Bitmap materialImage)
{
    Byte[] imageBytesPattern;
    if (materialImage.Width != width || materialImage.Height != height)
        return null;
    // extract bytes of pattern image. Stride should be the same.
    Int32 patternStride;
    imageBytesPattern = ImageUtils.GetImageData(materialImage, out patternStride, PixelFormat.Format32bppArgb);
    if (patternStride != stride)
        return null;
    // create new image for the black lines to paint over the pattern.
    Byte[] imageBytesBlack = new Byte[imageBytesPattern.Length];
    // Line start offset is set to 3 to immediately get the alpha component.
    Int32 lineOffsImg = 3;
    for (Int32 y = 0; y < height; y++)
    {
        Int32 curOffs = lineOffsImg;
        for (Int32 x = 0; x < width; x++)
        {
            Byte alpha = shapeImageBytes[curOffs];
            // copy alpha from shape image onto pattern image.
            imageBytesPattern[curOffs] = alpha;
            // copy either alpha or inverted brightness (whichever is lowest)
            // from the shape image onto black lines image as alpha, effectively
            // only retaining the visible black lines from the shape image.
            // I use curOffs - 1 (red) because it's the simplest operation.
            imageBytesBlack[curOffs] = Math.Min(alpha, (Byte)(255 - shapeImageBytes[curOffs - 1]));
            // Adjust offset to next pixel.
            curOffs += 4;
        }
        // Adjust line offset to next line.
        lineOffsImg += stride;
    }
    // Make a images out of the byte arrays.
    Bitmap bmCombined = BuildImage(imageBytesPattern, width, height, stride, PixelFormat.Format32bppArgb);
    using (Bitmap bmOverlay = BuildImage(imageBytesBlack, width, height, stride, PixelFormat.Format32bppArgb))
    {
        // set resolution of all images the same before combining,
        // to ensure correct drawing.
        bmCombined.SetResolution(materialImage.HorizontalResolution, materialImage.VerticalResolution);
        bmOverlay.SetResolution(materialImage.HorizontalResolution, materialImage.VerticalResolution);
        // paint black lines image onto alpha-adjusted pattern image.
        using (Graphics g = Graphics.FromImage(bmCombined))
            g.DrawImage(bmOverlay, 0, 0);
        // Return the new image.
        return bmCombined;
    }
}

帮助程序功能从图像中提取字节:

public static Byte[] GetImageData(Bitmap sourceImage, out Int32 stride, PixelFormat desiredPixelFormat)
{
    Int32 width = sourceImage.Width;
    Int32 height = sourceImage.Height;
    BitmapData sourceData = sourceImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, desiredPixelFormat);
    stride = sourceData.Stride;
    Byte[] data = new Byte[stride * height];
    Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
    sourceImage.UnlockBits(sourceData);
    return data;
}

帮助程序函数通过字节数组创建新图像:

public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat)
{
    Bitmap newImage = new Bitmap(width, height, pixelFormat);
    BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
    // Get actual data width.
    Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
    Int32 targetStride = targetData.Stride;
    Int64 scan0 = targetData.Scan0.ToInt64();
    // Copy per line, copying only data and ignoring any possible padding.
    for (Int32 y = 0; y < height; ++y)
        Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
    newImage.UnlockBits(targetData);
    return newImage;
}

我的测试工具中的结果:

“组合图像”“>

如您所见,黑线保留在图案的顶部。

© www.soinside.com 2019 - 2024. All rights reserved.