如何重新着色图片?(见图片)

14
我如何在程序中实现这种颜色替换? 将黑色替换为蓝色
这是我用来替换像素的函数:
Color.FromArgb(
    oldColorInThisPixel.R + (byte)((1 - oldColorInThisPixel.R / 255.0) * colorToReplaceWith.R),
    oldColorInThisPixel.G + (byte)((1 - oldColorInThisPixel.G / 255.0) * colorToReplaceWith.G),
    oldColorInThisPixel.B + (byte)((1 - oldColorInThisPixel.B / 255.0) * colorToReplaceWith.B)
    )

感谢你,CodeInChaos!


1
我为什么失去了一个赞,或者被踩了?哈哈... - Vercas
1
没有人会给你的问题点踩。我猜测当你编辑问题时,有人撤销了他们的赞同。无论如何,除非有人留下具体的建议或批评,否则这不值得担心。 :-) - Cody Gray
7个回答

11

计算新像素的公式为:

newColor.R = OldColor;
newColor.G = OldColor;
newColor.B = 255;

对任意颜色进行泛化:

我假设你想将白色映射到白色,黑色映射到该颜色。因此,公式为newColor = TargetColor + (White - TargetColor) * Input

newColor.R = OldColor + (1 - oldColor / 255.0) * TargetColor.R;
newColor.G = OldColor + (1 - oldColor / 255.0) * TargetColor.G;
newColor.B = OldColor + (1 - oldColor / 255.0) * TargetColor.B;

接着,只需对图像(字节数组)的像素进行迭代,并将它们写入新的RGB数组中。有许多关于如何将图像复制到字节数组并进行操作的线程。


1
这个答案对我来说看起来最好,但是那个蓝色可能是任何颜色。 - Vercas
1
@CodeInChaos,我正在测试这个公式。另外,你忘记将“.R”、“ .G”和“.B”添加到“oldColor”变量中了。 - Vercas
我没有将它们添加到oldColor中,因为我假设旧颜色只是一个字节,因为左侧图像只是灰度。而且我的代码只是伪代码。你不应该使用ColorGetPixel,因为那样会非常慢。你需要使用基于Bitmap.LockBits的东西,也许是复制到字节数组中。 - CodesInChaos
@misha 1)我假设InputColor(又名OldColor)只是一个字节,因为它是灰度图像。2)你的255有两个错误。你正在使用整数除法,所以除了255之外,您将目标颜色映射为0。并且用255.0替换我的1也是错误的。我的伪代码唯一的错误是没有明智的颜色结构会是可变的,并且我的最终结果是double,而我将其分配给了字节。 - CodesInChaos
@CodeInChaos 哎呀,我是指 newColor.R=TargetColor.R+(255.0-TargetColor.R)/255*InputColor.R。我以为 newColor.R 的范围在 [ 0, 255 ],但现在我发现它是 [ 0, 1.0 ]。 - mpenkov
显示剩余6条评论

9
最简单的方法是使用ColorMatrix来处理图像,您甚至可以实时预览所需效果,这就是许多图形编辑应用程序中制作颜色滤镜的方式。在这里这里,您可以找到使用C#中的Colormatrix进行颜色效果介绍。通过使用ColorMatrix,您可以制作自己想要的着色滤镜,如棕褐色、黑白、反转、范围、亮度、对比度、亮度、级别(通过多次传递)等。

编辑:这里有一个示例(更新 - 修正了颜色矩阵以将较暗的值移入蓝色而不是之前的除蓝色部分外的零化 - 并且 - 在上面的图片中添加了0.5f到蓝色,因为黑色变成了50%的蓝色):

var cm = new ColorMatrix(new float[][]
{
  new float[] {1, 0, 0, 0, 0},
  new float[] {0, 1, 1, 0, 0},
  new float[] {0, 0, 1, 0, 0},
  new float[] {0, 0, 0, 1, 0},
  new float[] {0, 0, 0.5f, 0, 1}
});

var img = Image.FromFile("C:\\img.png");
var ia = new ImageAttributes();
ia.SetColorMatrix(cm);

var bmp = new Bitmap(img.Width, img.Height);
var gfx = Graphics.FromImage(bmp);
var rect = new Rectangle(0, 0, img.Width, img.Height);

gfx.DrawImage(img, rect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, ia);

bmp.Save("C:\\processed.png", ImageFormat.Png);

我可能会使用颜色矩阵,但我不知道它是什么,也不知道如何将其用于我的目的。 - Vercas
1
上面的例子正好做了你上面的图像转换示例所做的事情。 - too
你可以尝试不同的数值,最后一行有三个数值相加得到RGB值(从-1到1的值意味着从-255到255的值),在对角线上是原始RGB值的乘积(如果你乘/加太多,像素值将会是0或255),在近似对角线处我设置了1来将值转移到蓝色。尝试一下,这是值得的。 - too

4

您需要在此处使用ColorMatrix。源图像是灰度的,其所有R、G和B值都相等。然后,只需将黑色替换为RGB =(0,0,255)以获得深蓝色,将白色替换为RGB =(255,255,255)以获得白色。因此,矩阵可以如下所示:

1 0 0 0 0       // not changing red
0 1 0 0 0       // not changing green
0 0 0 0 0       // B = 0
0 0 0 1 0       // not changing alpha
0 0 1 0 1       // B = 255

这个样例表单复制了右侧的图像:

public partial class Form1 : Form {
    public Form1() {
        InitializeComponent();
    }
    private Image mImage;
    protected override void OnPaint(PaintEventArgs e) {
        if (mImage != null) e.Graphics.DrawImage(mImage, Point.Empty);
        base.OnPaint(e);
    }
    private void button1_Click(object sender, EventArgs e) {
        using (var srce = Image.FromFile(@"c:\temp\grayscale.png")) {
            if (mImage != null) mImage.Dispose();
            mImage = new Bitmap(srce.Width, srce.Height);
            float[][] coeff = {
                            new float[] { 1, 0, 0, 0, 0 },
                            new float[] { 0, 1, 0, 0, 0 },
                            new float[] { 0, 0, 0, 0, 0 },
                            new float[] { 0, 0, 0, 1, 0 },
                            new float[] { 0, 0, 1, 0, 1 }};
            ColorMatrix cm = new ColorMatrix(coeff);
            var ia = new ImageAttributes();
            ia.SetColorMatrix(new ColorMatrix(coeff));
            using (var gr = Graphics.FromImage(mImage)) {
                gr.DrawImage(srce, new Rectangle(0, 0, mImage.Width, mImage.Height),
                    0, 0, mImage.Width, mImage.Height, GraphicsUnit.Pixel, ia);
            }
        }
        this.Invalidate();
    }
}

那个蓝色只是一个例子。要重新上色的图像可以有任何颜色,不仅限于灰度,要替换/添加的颜色也可以是任何颜色,也就是说R、G和B可以是任何值。我完全不理解这些颜色矩阵。 - Vercas
1
那么,这只是一个使用ColorMatrix替换颜色的示例。您需要自己想出系数来进行自己的替换。使用此教程以了解其工作原理:http://active.tutsplus.com/tutorials/effects/manipulate-visual-effects-with-the-colormatrixfilter-and-convolutionfilter/ - Hans Passant

3

这很大程度上取决于您的图像格式和最终格式。

还要看您想使用什么工具。您可以使用:

  • GDI
  • GD+
  • 图像处理库,如OpenCV

GDI非常快,但可能会很繁琐。您需要更改调色板。 GDI+在.NET中公开,可能会更慢,但更容易。 OpenCV非常好,但会增加依赖性。


(更新)

此代码将图像更改为蓝色调而不是灰色调 - 图像格式为32位ARGB:

private static unsafe void ChangeColors(string imageFileName)
{
    const int noOfChannels = 4;
    Bitmap img = (Bitmap) Image.FromFile(imageFileName);
    BitmapData data = img.LockBits(new Rectangle(0,0,img.Width, img.Height), ImageLockMode.ReadWrite, img.PixelFormat);
    byte* ptr = (byte*) data.Scan0;
    for (int j = 0; j < data.Height; j++)
    {
        byte* scanPtr = ptr + (j * data.Stride);
        for (int i = 0; i < data.Stride; i++, scanPtr++)
        {
            if (i % noOfChannels == 3)
            { 
                *scanPtr = 255;
                continue;
            }
            if (i % noOfChannels != 0)
            {
                *scanPtr = 0;
            }
        }
    }

    img.UnlockBits(data);
    img.Save(Path.Combine( Path.GetDirectoryName(imageFileName), "result.png"), ImageFormat.Png);
}

是的,我的朋友,但ImageFormat很重要。它是8位灰度?24位Windows位图?32位带有alpha通道?如果你告诉我想使用哪一个,我可以给你代码示例。 - Aliostad
嗯,我正在打开一张没有透明度的PNG图像,所以我猜它是24位。 - Vercas
正如我在被接受的答案中所评论的那样,蓝色只是一个例子。它可以是任何颜色,我的图像也不仅仅有白色,例如它也可能有一些红色。 - Vercas
请注意,您的字节按BGRA(而不是ARGB)排序,因此您可以想出各种组合。我的代码只是将红色和绿色通道设置为零,并最大化alpha通道。 - Aliostad

2
本文介绍了更多相关内容:http://www.codeproject.com/KB/GDI-plus/Image_Processing_Lab.aspx。使用AForge.NET库来对图像进行色调滤镜处理,达到类似的效果。
  // create filter

  AForge.Imaging.Filters.HSLFiltering filter =
      new AForge.Imaging.Filters.HSLFiltering( );
  filter.Hue = new IntRange( 340, 20 );
  filter.UpdateHue = false;
  filter.UpdateLuminance = false;
  // apply the filter

  System.Drawing.Bitmap newImage = filter.Apply( image );

那么,这会将范围内的任何颜色更改为我的颜色吗? - Vercas
我相信这个库利用色调范围,可以轻松地对使用相机捕捉的图像进行颜色替换。想想绿幕,绿色在图像中并不是均匀的,而是根据阴影等落在某个范围内。 - Larry Smithmier

1

这也取决于你想要什么:你是想保留原始数据并只调整它的显示方式吗?在WPF中使用效果或像素着色器可能会起到作用,并且速度非常快。


不,我需要调整原始内容。 - Vercas

-1
如果有任何 Android 开发人员看到这篇文章,这是我使用 CodesInChaos 的公式和 Android 图形类 ColorMatrix 和 ColorMatrixColorFilter 来实现图像灰度和着色的方法。
感谢您的帮助!
public static ColorFilter getColorFilter(Context context) {
    final int tint = ContextCompat.getColor(context, R.color.tint);

    final float R = Color.red(tint);
    final float G = Color.green(tint);
    final float B = Color.blue(tint);

    final float Rs = R / 255;
    final float Gs = G / 255;
    final float Bs = B / 255;

    // resultColor = oldColor + (1 - oldColor/255) * tintColor
    final float[] colorTransform = {
            1, -Rs, 0, 0, R,
            1, -Gs, 0, 0, G,
            1, -Bs, 0, 0, B,
            0, 0, 0, 0.9f, 0};

    final ColorMatrix grayMatrix = new ColorMatrix();
    grayMatrix.setSaturation(0f);
    grayMatrix.postConcat(new ColorMatrix(colorTransform));
    return new ColorMatrixColorFilter(grayMatrix);
}

ColorFilter 可以应用于 ImageView 上。

imageView.setColorFilter(getColorFilter(imageView.getContext()));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接