我遇到性能问题。
对于鞋垫模型配置器,我们要上传一个样片,许多材料图像要与该样片图像融合。
我应将实物图像上的每个白色像素替换为实物图像上的相应像素。
由于实物图像不是单色,所以我不能仅用另一种单色替换所有白色。
图像大小相同。因此,如果颜色对于块图像而言不是透明的,并且在实体图像上具有相同的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像素。
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);
}
}
}
我找到了这个解决方案,它要快得多。
但是它使用过多的资源。
使用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);
}
替换白色是一种可能,但不是很漂亮。根据那里的图像,理想的解决方案是使用正确的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; }
我的测试工具中的结果:
“>
如您所见,黑线保留在图案的顶部。