基于一些给定的参考元素插值颜色矩阵的元素

het*_*sch 5 python numpy image-processing matrix scipy

这或多或少是对从4角色插值的二维颜色渐变(256x256矩阵)的后续问题,今天jadsq深刻回答了这个问题.

对于线性渐变,前面的答案非常有效.但是,如果想要更好地控制渐变的停止颜色,这种方法似乎不太实用.在这种情况下可能有用的是在矩阵(查找表)中有一些参考色点,用于插入查找表中空位置的颜色值.我的意思可能更容易从下面的图像中读出来.

在此输入图像描述

整个想法来自http://cartography.oregonstate.edu/pdf/2006_JennyHurni_SwissStyleShading.pdf第4页到第6页.我已经阅读了论文,理论上我理解发生了什么但由于我的经验不足而惨遭失败插值方法,说实话,一般数学技能.可能还有兴趣的是,他们使用S形高斯钟作为插值方法(第6页).他们认为高斯加权产生了视觉上最好的结果并且计算简单(方程1,对于每256个单元256个表,k = 0.0002).


编辑(更好的插图):

用于插值颜色的加权函数

公式1


我已经将其呈现方法的其他部分放在适当位置,但填充矩阵中的空值确实是一个关键部分,并使我无法继续.再一次,谢谢你的帮助!

我现在拥有的:

#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt 

# the matrix with the reference color elements
ref=np.full([7, 7, 3], [255,255,255], dtype=np.uint8)
ref[0][6] = (239,238,185)
ref[1][1] = (120,131,125)
ref[4][6] = (184,191,171)
ref[6][2] = (150,168,158)
ref[6][5] = (166,180,166)

# s = ref.shape
#
# from scipy.ndimage.interpolation import zoom
# zooming as in https://stackoverflow.com/a/39485650/1230358 doesn't seem to work here anymore, because we have no corner point as reference but randomly distributed points within the matrix. As far as I know ...
# zoomed=zoom(ref,(256/s[0],256/s[1],1),order=1)

plt.subplot(211)
plt.imshow(ref,interpolation='nearest')
# plt.subplot(212)
# plt.imshow(zoomed,interpolation='nearest')
plt.show()
Run Code Online (Sandbox Code Playgroud)

Spe*_*tre 5

首先是一些问题,以便更好地澄清您的问题:

  • 你想要什么样的插值:线性/立方/其他?
  • 有什么限制点?例如,总是只有这些控制点封装的单个区域,或者内部也可能存在点?

对于简单的线性插值和任意(但至少3点不在一行)我会尝试这样:

  1. 三角控制点区域

    覆盖整个定义区域的非重叠三角形.

  2. 渲染三角形

    所以只需栅格化看算法填充三角形和所有子链接.您还应该插入R,G,B坐标.

  3. 创建2个渐变副本,并用H和第二个用V线外推

    因此,扫描渐变的所有H水平线,如果发现2个已知像素彼此足够远(例如,梯度大小的四分之一或一半),则推断整行未知颜色.因此,如果找到已知端点(红色),(x0,y,r0,g0,b0),(x1,y,r1,g1,b1)则在同一行中设置所有未知颜色:

    r = r0+(r1-r0)*(x-x0)/(x1-x0)
    g = g0+(g1-g0)*(x-x0)/(x1-x0)
    b = b0+(b1-b0)*(x-x0)/(x1-x0)
    
    Run Code Online (Sandbox Code Playgroud)

    同样,现在在V垂直线的渐变副本中也是如此.所以这些点现在是(x,y0,r0,g0,b0),(x,y1,r1,g1,b1)`和外推:

    r = r0+(r1-r0)*(y-y0)/(y1-y0)
    g = g0+(g1-g0)*(y-y0)/(y1-y0)
    b = b0+(b1-b0)*(y-y0)/(y1-y0)
    
    Run Code Online (Sandbox Code Playgroud)

    在此之后比较两个副本并且如果在两者中计算未知点,则将其设置为目标梯度图像中的两种颜色的平均值.循环整个过程(#3)直到没有添加新的渐变像素.

  4. 其余部分使用单个外推颜色

    根据您如何定义控制点,某些区域将只有1个外推颜色(来自H或V线而不是两者),因此仅使用单个计算颜色(在#3完成后).

这是我所说的一个例子:

概观

如果你想要一些简单的东西(但不精确),那么你可以将已知的控制点颜色(使用平滑滤波器)渗透到相邻像素,直到整个渐变被填充和饱和.

  1. 用预定义的颜色填充未知的渐变像素,意味着不计
  2. 将每个像素设置为其计算邻居的平均值

    您可以在单独的图像中执行此操作以避免移位.

  3. 将控制点设置回原始颜色

  4. 循环#2直到区域填充/饱和/或预定义的迭代次数

[Edit1]第二个解决方案

好吧我把它放在C++中,你的点/颜色和渐变大小就是这样看起来(我在没有重量的情况下出血100次,4个邻居出血):

流血的

左边的图像是输入矩阵,如果像素是参考点,计算或未定义,我将其编码为alpha通道(最高8位).右侧图像是在出血100次后.出血很简单,只需取任何非参考点,并将其重新计算为周围所有可用像素的平均值及其自身(忽略任何未定义的颜色).

这里的C++代码你可以忽略渲染的GDI东西(小心我的渐变贴图x首先有坐标y!)

//---------------------------------------------------------------------------
const int mxs=7,mys=7,msz=16;   // gradient resolution x,y and square size for render
DWORD map[mxs][mys];            // gradient matrix ... undefined color is >= 0xFF000000
// 0x00?????? - reference color
// 0xFF?????? - uncomputed color
// 0xFE?????? - bleeded color
//---------------------------------------------------------------------------
void map_clear()    // set all pixels as uncomputed (white with alpha=255)
    {
    int x,y;
    for (x=0;x<mxs;x++)
     for (y=0;y<mys;y++)
      map[x][y]=0xFFFFFFFF;
    }
void map_bleed()    // bleed computed colors
    {
    int x,y,r,g,b,n;
    DWORD tmp[mxs][mys],c;
    for (x=0;x<mxs;x++)
     for (y=0;y<mys;y++)
        {
        c=map[x][y];
        n=0; r=0; g=0; b=0; if (DWORD(c&0xFF000000)==0) { tmp[x][y]=c; continue; }      if (DWORD(c&0xFF000000)!=0xFF000000) { r+=c&255; g+=(c>>8)&255; b+=(c>>16)&255; n++; }
        x++;      if ((x>=0)&&(x<mxs)&&(y>=0)&&(y<mys)) c=map[x][y]; else c=0xFF000000; if (DWORD(c&0xFF000000)!=0xFF000000) { r+=c&255; g+=(c>>8)&255; b+=(c>>16)&255; n++; }
        x--; y--; if ((x>=0)&&(x<mxs)&&(y>=0)&&(y<mys)) c=map[x][y]; else c=0xFF000000; if (DWORD(c&0xFF000000)!=0xFF000000) { r+=c&255; g+=(c>>8)&255; b+=(c>>16)&255; n++; }
        x--; y++; if ((x>=0)&&(x<mxs)&&(y>=0)&&(y<mys)) c=map[x][y]; else c=0xFF000000; if (DWORD(c&0xFF000000)!=0xFF000000) { r+=c&255; g+=(c>>8)&255; b+=(c>>16)&255; n++; }
        x++; y++; if ((x>=0)&&(x<mxs)&&(y>=0)&&(y<mys)) c=map[x][y]; else c=0xFF000000; if (DWORD(c&0xFF000000)!=0xFF000000) { r+=c&255; g+=(c>>8)&255; b+=(c>>16)&255; n++; }
        y--;      if (!n) { tmp[x][y]=0xFFFFFFFF; continue; }
        c=((r/n)|((g/n)<<8)|((b/n)<<16))&0x00FFFFFF;
        tmp[x][y]=c;
        }
    // copy tmp back to map
    for (x=0;x<mxs;x++)
     for (y=0;y<mys;y++)
      map[x][y]=tmp[x][y];
    }
void map_draw(TCanvas *can,int x0,int y0)   // just renders actual gradient map onto canvas (can ignore this)
    {
    int x,y,xx,yy;
    for (x=0,xx=x0;x<mxs;x++,xx+=msz)
     for (y=0,yy=y0;y<mys;y++,yy+=msz)
        {
        can->Pen->Color=clBlack;
        can->Brush->Color=map[x][y]&0x00FFFFFF;
        can->Rectangle(xx,yy,xx+msz,yy+msz);
        }
    }
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

这里的用法(你的例子):

// clear backbuffer
bmp->Canvas->Brush->Color=clBlack; 
bmp->Canvas->FillRect(TRect(0,0,xs,ys));

// init your gradient with reference points
map_clear();
//  x  y       R     G        B
map[6][0] = (239)|(238<<8)|(185<<16);
map[1][1] = (120)|(131<<8)|(125<<16);
map[6][4] = (184)|(191<<8)|(171<<16);
map[2][6] = (150)|(168<<8)|(158<<16);
map[5][6] = (166)|(180<<8)|(166<<16);
map_draw(bmp->Canvas,msz,msz); // render result (left)
// bleed
for (int i=0;i<100;i++) map_bleed();
map_draw(bmp->Canvas,(mxs+2)*msz,msz); // render result (right)

// refresh window with backbufer (anti-flickering)
Main->Canvas->Draw(0,0,bmp);
Run Code Online (Sandbox Code Playgroud)

您可以再次忽略所有渲染内容.出血的数量应该比对角线的像素大2倍,因此出血覆盖了所有像素.迭代越多,我尝试的结果越饱和100,结果看起来不错......所以我不再玩它了...

[Edit2]和这里的第二种方法的算法

  1. 将标志添加到插值矩阵

    你需要知道,如果像素reference,undefinedinterpolated.您可以将此编码为alpha通道,或使用遮罩(单独的2D矩阵).

  2. 出血/光滑的基质

    基本上对于每个非reference像素计算其新值作为所有非undefined像素(4/8个邻居)和其位置的平均值.不要使用undefined像素并将计算值存储到临时矩阵(不要弄乱下一个像素,否则出血/平滑通常会对角线移动像素).这样未定义的像素区域将缩小1个像素.完成整个矩阵后,将临时矩阵的内容复制到原始矩阵(或交换指针).

  3. 循环#2直到结果饱和或特定的迭代次数

    计数的数量应该比将参考像素传播到整个矩阵的对角线像素的数量大2倍.饱和度检查可以在#2中完成,同时将临时数组复制到原始数组(可以在帧之间进行abs差异,如果为0或接近停止).