【问题标题】:Question regarding color histogram based methods of generating color lookup-up tables关于基于颜色直方图生成颜色查找表的方法的问题
【发布时间】:2020-02-12 10:16:22
【问题描述】:

我有一段代码需要符合研究论文对 256 条目 LUT 的颜色量化算法的实现,该 LUT 的 24 位颜色条目来自“人口计数”颜色直方图算法。问题是我不知道作者最初是如何实现他们的直方图算法的——这篇论文有点模棱两可。目前,我根据像素的原始 RGB 24 位颜色三元组索引一个 2^24 整数条目数组,并增加数组中特定的索引条目。然后我对直方图进行排序,然后通过将 512 个颜色计数的块放入 bin 并取 bin 中所有颜色的算术平均值,将其组织成一个有效的 15 位颜色空间。然后,我将 256 个平均颜色值(从最大颜色计数开始)按颜色计数的递减顺序填充到 256 个条目的 24 位颜色 LUT 中。输出非常令人失望,而且质量低下。我知道矢量量化或类似中值切割的东西会更好,但我不得不用直方图来做。我使用谷歌广泛搜索了“人口计数”直方图算法,但没有一个搜索结果很有帮助。

作为参考,我将包含原始的 512x512 像素 24 位彩色图像及其基于直方图的颜色 LUT 对应物:

如果有人能就在哪里寻找合适的算法提供一些想法或建议,我将不胜感激。

谢谢,

jdb2

【问题讨论】:

  • FWIW,我认为使用直方图生成调色板(即一种矢量量化)图像是一种相对较差的方式。虽然老,恕我直言,Paul Heckbert 的*"Color Image Quantization for Frame Buffer Display" * 会做得更好,而且 IIRC 很容易实现。您可以使用主成分分析(例如,Xiaolin Wu 的方法)以及应用 K-Means 等来做得更好。

标签: graphics histogram rgb lookup quantization


【解决方案1】:

试试这个Effective gif/image color quantization? 它也是基于直方图颜色量化的,与您的方法非常相似,但它直接从 15 位颜色创建直方图以节省空间,并且不使用 bin,而是按出现次数对颜色进行排序,并使用最小距离在调色板阈值中使用颜色以避免几乎重复的颜色...几年前我为我的 GIF 编码器库开发了它...

如果我将此作为输入(转换为 jpg):

并在没有抖动的情况下使用我的算法我得到了这个结果:

启用抖动后,我得到了这个结果:

正如您在猫耳上看到的,抖动效果要好得多,但即使没有抖动,结果也比您的要好。

然而,多年来,调色板计算代码(从链接答案中发布的那个)演变为这个(C++):

void gif::compute_palette0()
    {
    int x,y,r0,g0,b0,r,g,b,a,aa,c,e,hists;
    DWORD i,i0,cc;
    union { DWORD dd; BYTE db[4]; } c0,c1;
    DWORD his[32768];
    DWORD idx[32768];
    // 15bit histogram
    for (x=0;x<32768;x++) { his[x]=0; idx[x]=x; }
    for (y=0;y<ys;y++)
     for (x=0;x<xs;x++)
        {
        cc=pic.pyx[y][x];
        cc=((cc>>3)&0x1F)|((cc>>6)&0x3E0)|((cc>>9)&0x7C00);
        if (his[cc]<0xFFFFFFFF) his[cc]++;
        }
    // add RGB shades combinations for dithering
    if (_dither)
        {
        x=xs*ys;    // max possible count to make as start colors in palette
        for (r=0;r<32;r+=10)
         for (g=0;g<32;g+=10)
          for (b=0;b<32;b+=10,x++)
           his[(r<<10)|(g<<5)|( b)]=x;
        }
    // set recolor as unused
    for (r0=0;r0<32;r0++)
     for (g0=0;g0<32;g0++)
      for (b0=0;b0<32;b0++)
       recolor[r0][g0][b0]=255;
    // remove zeroes
    for (x=0,y=0;y<32768;y++)
        {
        his[x]=his[y];
        idx[x]=idx[y];
        if (his[x]) x++;
        } hists=x;
    // sort by hist
    for (i=1,e=hists;i;e--)
     for (i=0,x=0,y=1;y<e;x++,y++)
      if (his[x]<his[y])
        {
        i=his[x]; his[x]=his[y]; his[y]=i;
        i=idx[x]; idx[x]=idx[y]; idx[y]=i; i=1;
        }
    // set lcolor color palete
    for (i0=0,x=0;x<hists;x++) // main colors
        {
        cc=idx[x];
        b= cc     &31;
        g=(cc>> 5)&31;
        r=(cc>>10)&31;
        c0.db[0]=b;
        c0.db[1]=g;
        c0.db[2]=r;
        c0.dd=(c0.dd<<3)&0x00F8F8F8;
        // skip if similar color already in lcolor[]
        for (a=0,i=0;i<i0;i++)
            {
            c1.dd=lcolor[i];
            aa=int(BYTE(c1.db[0]))-int(BYTE(c0.db[0])); if (aa<=0) aa=-aa; a =aa;
            aa=int(BYTE(c1.db[1]))-int(BYTE(c0.db[1])); if (aa<=0) aa=-aa; a+=aa;
            aa=int(BYTE(c1.db[2]))-int(BYTE(c0.db[2])); if (aa<=0) aa=-aa; a+=aa;
            if (a<=16) { a=1; break; } a=0; // *** treshold ***
            }
        if (a) recolor[r][g][b]=i;
        else{
            recolor[r][g][b]=i0;
            lcolor[i0]=c0.dd; i0++;
            if (i0>=DWORD(lcolors)) { x++; break; }
            }
        }   // i0 = new color table size
    for (;x<hists;x++)  // minor colors
        {
        cc=idx[x];
        b= cc     &31;
        g=(cc>> 5)&31;
        r=(cc>>10)&31;
        c0.db[0]=b;
        c0.db[1]=g;
        c0.db[2]=r;
        c0.dd=(c0.dd<<3)&0x00F8F8F8;
        // find closest color
        int dc=-1; DWORD ii=0;
        for (a=0,i=0;i<i0;i++)
            {
            c1.dd=lcolor[i];
            aa=int(BYTE(c1.db[0]))-int(BYTE(c0.db[0])); if (aa<=0) aa=-aa; a =aa;
            aa=int(BYTE(c1.db[1]))-int(BYTE(c0.db[1])); if (aa<=0) aa=-aa; a+=aa;
            aa=int(BYTE(c1.db[2]))-int(BYTE(c0.db[2])); if (aa<=0) aa=-aa; a+=aa;
            if ((dc<0)||(dc>a)) { dc=a; ii=i; }
            }
        recolor[r][g][b]=ii;
        }
    encode_palette_compute(true);

    if ((frame)&&(hists<lcolors))
     for (lcolor_bits=1,lcolors=1<<lcolor_bits;lcolors<hists;lcolors<<=1,lcolor_bits++);
    // compute recolor for 16 base colors for all yet unused colors
    for (r0=0;r0<32;r0++)
     for (g0=0;g0<32;g0++)
      for (b0=0;b0<32;b0++)
       if (recolor[r0][g0][b0]==255)
        {
        // find closest color
        for (i=0,c=-1;i<16;i++)
            {
            c0.dd=lcolor[i];
            b=WORD(c0.db[0])>>3;
            g=WORD(c0.db[1])>>3;
            r=WORD(c0.db[2])>>3;
            a=(r-r0); aa =a*a;
            a=(g-g0); aa+=a*a;
            a=(b-b0); aa+=a*a;
            if ((c<0)||(e>aa)) { e=aa; c=i; }
            }
        recolor[r0][g0][b0]=c;
        }
    }

我的gif 类看起来像这样(因此您可以提取配置和使用的变量...):

class gif
    {
public:
    // IO interface
    file_cache<4<<20> fi,fo;        // file cache
    BYTE dat[256];                  // internal buffer 256 Bytes needed

    // Performance counter
    double Tms,tms,tdec,tenc;       // perioda citaca [ms], zmerany cas [ms],cas encodovania [ms]
    void tbeg();                    // zaciatok merania
    void tend();                    // koniec merania

    // image data
    gif_frame32 pic,pic0;           // actual and restore to 32bit frames
    gif_frame8  pic1;               // 8bit input conversion frame

    int xs,ys;                      // resolution
    int *py;                        // interlace table

    // colors (colors are computed from color_bits)
    DWORD gcolor[256];              //hdr
    DWORD lcolor[256];              //img
    BYTE  recolor[32][32][32];      //encode reduce color table
    int scolors,scolor_bits;        //hdr screen color depth
    int gcolors,gcolor_bits;        //hdr global pallete
    int lcolors,lcolor_bits;        //img/hdr local palette

    // info
    bool _89a;                      //hdr extensions present?
    bool _interlaced;               //img interlaced frame?
    bool _gcolor_table;             //hdr
    bool _gcolor_sorted;            //hdr
    bool _lcolor_table;             //img local palette present?
    bool _lcolor_sorted;            //img local palette colors sorted?
    int  cpf,cpf_error;             //clears per frame counter,clear_errors total
    // debug
    bool _draw_palette;             //draw pallete?

    // animation
    int frame,disposal;             // frame ix,disposal of frame
    double t,tn;                    // animation time,next frame time

    // encode config
    int _force_disposal;            // -1 or forced disposal
    bool _precomputed_palette;      // if true recolor and global palete is already set before encoding
    bool _dither;                   // dither colors?

    // inter thread comm
    volatile bool _image_copied;    // flag that source image is not needed anymore while encoding

    // temp dictionary for dec/enc
    gif_str dict[_gif_maxdecode];
    DWORD dicts,code_clr,code_end,code_min;
    // temp dictionary speed up tables (encoding)
    WORD dtab[256][_gif_maxencode],dnum[256],dmask[256];    // dtab[i][dnum[i]] all dictionary codes (sorted by code) starting with i for encode speed up, 1<<dmask[i]<=dnum[i]

    #pragma pack(1)
    struct __hdr
        {
        // Header
        BYTE Signature[3];      /* Header Signature (always "GIF") */
        BYTE Version[3];        /* GIF format version("87a" or "89a") */
        // Logical Screen Descriptor
        WORD xs;
        WORD ys;
        BYTE Packed;            /* Screen and Color Map Information */
        BYTE BackgroundColor;   /* Background Color Index */
        BYTE AspectRatio;       /* Pixel Aspect Ratio */
        __hdr(){}; __hdr(__hdr& a){ *this=a; }; ~__hdr(){}; __hdr* operator = (const __hdr *a) { *this=*a; return this; }; /*__hdr* operator = (const __hdr &a) { ...copy... return this; };*/
        };
    struct _hdr:__hdr
        {
        DWORD adr,siz;
        _hdr(){}; _hdr(_hdr& a){ *this=a; }; ~_hdr(){}; _hdr* operator = (const _hdr *a) { *this=*a; return this; }; /*_hdr* operator = (const _hdr &a) { ...copy... return this; };*/
        } hdr;

    struct __img
        {
        // Logical Image Descriptor
        BYTE Separator;         /* Image Descriptor identifier 0x2C */
        WORD x0;                /* X position of image on the display */
        WORD y0;                /* Y position of image on the display */
        WORD xs;                /* Width of the image in pixels */
        WORD ys;                /* Height of the image in pixels */
        BYTE Packed;            /* Image and Color Table Data Information */
        __img(){}; __img(__img& a){ *this=a; }; ~__img(){}; __img* operator = (const __img *a) { *this=*a; return this; }; /*__img* operator = (const __img &a) { ...copy... return this; };*/
        };
    struct _img:__img
        {
        DWORD adr,siz;
        _img(){}; _img(_img& a){ *this=a; }; ~_img(){}; _img* operator = (const _img *a) { *this=*a; return this; }; /*_img* operator = (const _img &a) { ...copy... return this; };*/
        } img;

    struct __gfxext
        {
        BYTE Introducer;        /* Extension Introducer (always 21h) */
        BYTE Label;             /* Graphic Control Label (always F9h) */
        BYTE BlockSize;         /* Size of remaining fields (always 04h) */
        BYTE Packed;            /* Method of graphics disposal to use */
        WORD DelayTime;         /* Hundredths of seconds to wait    */
        BYTE ColorIndex;        /* Transparent Color Index */
        BYTE Terminator;        /* Block Terminator (always 0) */
        __gfxext(){}; __gfxext(__gfxext& a){ *this=a; }; ~__gfxext(){}; __gfxext* operator = (const __gfxext *a) { *this=*a; return this; }; /*__gfxext* operator = (const __gfxext &a) { ...copy... return this; };*/
        };
    struct _gfxext:__gfxext
        {
        DWORD adr,siz;
        _gfxext(){}; _gfxext(_gfxext& a){ *this=a; }; ~_gfxext(){}; _gfxext* operator = (const _gfxext *a) { *this=*a; return this; }; /*_gfxext* operator = (const _gfxext &a) { ...copy... return this; };*/
        } gfxext;

    struct __txtext
        {
        BYTE Introducer;        /* Extension Introducer (always 21h) */
        BYTE Label;             /* Extension Label (always 01h) */
        BYTE BlockSize;         /* Size of Extension Block (always 0Ch) */
        WORD TextGridLeft;      /* X position of text grid in pixels */
        WORD TextGridTop;       /* Y position of text grid in pixels */
        WORD TextGridWidth;     /* Width of the text grid in pixels */
        WORD TextGridHeight;    /* Height of the text grid in pixels */
        BYTE CellWidth;         /* Width of a grid cell in pixels */
        BYTE CellHeight;        /* Height of a grid cell in pixels */
        BYTE TextFgColorIndex;  /* Text foreground color index value */
        BYTE TextBgColorIndex;  /* Text background color index value */
//      BYTE *PlainTextData;    /* The Plain Text data */
//      BYTE Terminator;        /* Block Terminator (always 0) */
        __txtext(){}; __txtext(__txtext& a){ *this=a; }; ~__txtext(){}; __txtext* operator = (const __txtext *a) { *this=*a; return this; }; /*__txtext* operator = (const __txtext &a) { ...copy... return this; };*/
        };
    struct _txtext:__txtext
        {
        DWORD adr,siz;
        AnsiString dat;
        _txtext(){}; _txtext(_txtext& a){ *this=a; }; ~_txtext(){}; _txtext* operator = (const _txtext *a) { *this=*a; return this; }; /*_txtext* operator = (const _txtext &a) { ...copy... return this; };*/
        };
    List<_txtext> txtext;

    struct __remext
        {
        BYTE Introducer;        /* Extension Introducer (always 21h) */
        BYTE Label;             /* Comment Label (always FEh) */
//      BYTE *CommentData;      /* Pointer to Comment Data sub-blocks */
//      BYTE Terminator;        /* Block Terminator (always 0) */
        __remext(){}; __remext(__remext& a){ *this=a; }; ~__remext(){}; __remext* operator = (const __remext *a) { *this=*a; return this; }; /*__remext* operator = (const __remext &a) { ...copy... return this; };*/
        };
    struct _remext:__remext
        {
        DWORD adr,siz;
        AnsiString dat;
        _remext(){}; _remext(_remext& a){ *this=a; }; ~_remext(){}; _remext* operator = (const _remext *a) { *this=*a; return this; }; /*_remext* operator = (const _remext &a) { ...copy... return this; };*/
        };
    List<_remext> remext;

    struct __appext
        {
        BYTE Introducer;        /* Extension Introducer (always 21h) */
        BYTE Label;             /* Extension Label (always FFh) */
        BYTE BlockSize;         /* Size of Extension Block (always 0Bh) */
        CHAR Identifier[8];     /* Application Identifier */
        BYTE AuthentCode[3];    /* Application Authentication Code */
//      BYTE *ApplicationData;  /* Point to Application Data sub-blocks */
//      BYTE Terminator;        /* Block Terminator (always 0) */
        __appext(){}; __appext(__appext& a){ *this=a; }; ~__appext(){}; __appext* operator = (const __appext *a) { *this=*a; return this; }; /*__appext* operator = (const __appext &a) { ...copy... return this; };*/
        };
    struct _appext:__appext
        {
        DWORD adr,siz;
        AnsiString dat;
        _appext(){}; _appext(_appext& a){ *this=a; }; ~_appext(){}; _appext* operator = (const _appext *a) { *this=*a; return this; }; /*_appext* operator = (const _appext &a) { ...copy... return this; };*/
        };
    List<_appext> appext;
    #pragma pack()

    gif();
    gif(gif& a);
    ~gif();
    gif* operator = (const gif *a);
    //gif* operator = (const gif &a);

    void _resize(int _xs,int _ys);                  // resize buffers
    void load_beg(AnsiString filename);             // open GIF file for decoding
    void decode(int _ignore_delay=0);               // decode frame from GIF, if _ignore_delay then ignore realtime
    void load_end();                                // close GIF file
    void save_beg(AnsiString filename);             // create new GIF file for encoding
    void compute_palette0();                        // compute palette from frame method 0
    void compute_palette1();                        // compute palette from frame method 1
    void encode_palette_RGB256();                   // set RGB combinations as 256 color palette as predefined global only palette
    void encode_palette_VGA256();                   // set default 256 color VGA palette as predefined global only palette
    void encode_palette_compute(bool _local);       // compute recolor[][][] from palette
    void encode(const gif_frame32 &src,int dt=0);   // encode frame to GIF , dt is delay in [ms] instead of realtime in range <10 .. 655350> [ms]
//  void encode(int dst_xs,int dst_ys,TCanvas *src,int src_x0,int src_y0,int src_x1,int src_y1,int dt=0);   // encode frame to GIF , dt is delay in [ms] instead of realtime in range <10 .. 655350> [ms]
    void save_end();                                // finalize and close GIF file
    void draw_info(int x,int y,TCanvas *can);
    void draw_info(int x,int y,Graphics::TBitmap *bmp);
    void configure(gif &src);                       // set all encoding variables from src (for multithreaded encoding)
    };

【讨论】:

  • 我的问题太长,无法放入评论框中,所以我不得不将其分解。这是第 1 部分: --- 嗯,我使用的是 ANSI C 中的 FreeImage 库,所以我不必处理加载和保存图像文件的细节。从您引用的帖子中,我有几个问题:“转换为 15 位 rgb”我已经可以轻松做到这一点,而且我的代码仅使用每像素 15 位颜色量化创建调试图像,看起来不错。 “做直方图”是什么类型的? (在评论的第 2 部分继续...)
  • (评论第 2 部分)“15 位 rgb 导致 32768 个条目,因此创建 2 个数组 his[32768] 是像素数(直方图) idx[32768] 是索引 = 颜色值”在我的代码中,具有保持与颜色值关联的颜色计数的结构。我不确定你为什么使用两个数组。 “在计算颜色时,如果使用低位计数或高分辨率,确保计数器不会溢出”我通过索引 2^24 条目数组并增加索引颜色的计数来计算颜色。 (在评论的第 3 部分继续...)
  • (评论第 3 部分)“对数组进行重新排序,因此 his[] 中的零位于数组的末尾”我不太确定我是否在这里关注你。你的意思是对数组进行排序吗? “还计算 his[] 中的非零条目并将其称为 hists”我已经在我的代码中根据其设计的性质这样做了。 “(索引)排序 hist[],idx[] 所以 hist[] 按降序排列;”不太清楚你在这里说什么。在我的情况下,“创建 N 调色板”N 将是 256,而且,在这个阶段,图像质量会大幅下降。 (在评论第 4 部分继续...)
  • (评论第 4 部分)“取颜色 idx[i] (i={ 0,1,2,3,...,hists-1 }) 并查看您的调色板中是否不相似颜色。如果忽略此颜色(将其设置为找到的最接近的颜色),否则将其添加到调色板。如果您达到 N 颜色停止“我已经使用平方欧几里德距离度量来做到这一点 - 粗略但实用。 “创建颜色映射”注意很清楚你在这里的意思。 “所以取每种颜色并在调色板中找到最接近的颜色(这可以在步骤 5 中部分完成)我将此表称为 recolor[32][32][32]” 再次,不太清楚你的意思。 “重新着色图像”? ://
  • @jdb2 当你做一个 15 位直方图时,his[32768] 就像桶排序......当我对我需要的 his[] 降序排序时,保存所有可能的颜色计数(即使未使用为 0 计数)要知道哪个条目是针对哪个颜色的,因为排序后数组中的索引不再与idx[] 的颜色相同,所以无论您在his[] 上执行什么交换/重新排序,您也可以在idx[] 上执行@然后将 987654336@ 用作列表(不再是桶排序),其中颜色 idx[i] 具有 his[i] 计数。如果你只对使用过的颜色做直方图,那么它会慢得多....
猜你喜欢
  • 2012-01-18
  • 1970-01-01
  • 2020-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-22
相关资源
最近更新 更多