$$ \text{New_color}_j = \text{Old_color}_j + \alpha \times H(i, j) (\text{Color} - \text{Old_color}_j)$$

$$ H(i, j)  = \left\{   \begin{matrix} 1 & \text{if} ~| i - j  | \le R \\ 0 & \text{if} ~| i- j |>R   \end{matrix}\right.$$

/*
 * NeuQuant: Anthony Dekker's neural-network color quantizer.
 * Call ProcessImage() once, then QuantizeColor().
 */
class NeuQuant {
public:
    NeuQuant(int nColors, int sampleFactor);
    std::vector<RGBQUAD> ProcessImage(const CRaster& raster);
    int QuantizeColor(BYTE r, BYTE g, BYTE b) const;
private:
    enum {
        CYCLE_COUNT = 100,
    };
    struct Neuron {
        float b, g, r;
        int paletteIndex;
    };
    void InitNetwork();
    void Learn();
    int  Contest(int b, int g, int r);
    void AlterSingle(float alpha, int index, int b, int g, int r);
    void AlterNeighbours(int radius, int index, int b, int g, int r);
    void BuildIndex();
    std::vector<RGBQUAD> GetPalette() const;

    int                  m_ColorCount;
    int                  m_sampleFactor;
    int                  m_netIndex[256];
    std::vector<Neuron>  m_network;			
    std::vector<float>   m_bias;			
    std::vector<float>   m_frequency;		
    std::vector<float>   m_radiusPower;		
    std::vector<BYTE>    m_image;
    bool                 m_processed;
};
namespace {
const int PRIME1 = 499;
const int PRIME2 = 491;
const int PRIME3 = 487;
const int PRIME4 = 503;
const int   MIN_PICTURE_BYTES = 3 * PRIME4;
const float INITIAL_ALPHA     = 1.0f;
const float FREQUENCY_DECAY   = 1.0f / 1024.0f;
const float BETA              = 1.0f / 1024.0f;
const float GAMMA             = 1024.0f;
} // namespace
NeuQuant::NeuQuant(int nColors, int sampleFactor)
    : m_sampleFactor(sampleFactor < 1 ? 1 : sampleFactor),
      m_processed(false) {    
    m_ColorCount = max(1, min(256, nColors));
    
    m_network.resize(m_ColorCount);
    m_bias.resize(m_ColorCount);		
    m_frequency.resize(m_ColorCount);	
    m_radiusPower.resize(m_ColorCount >> 3);
}
// 다른 image에서 재사용할 수 있게;
std::vector<RGBQUAD> NeuQuant::ProcessImage(const CRaster& raster) {
    if (raster.IsEmpty() || raster.GetBPP() != 24) 
        return std::vector<RGBQUAD> ();
    
    m_processed = false;
    CSize sz = raster.GetSize();
    m_image.clear();
    m_image.reserve(sz.cx * sz.cy * 3);
    for (int y = 0; y < sz.cy; ++y) {
        const BYTE *q = (BYTE *)raster.GetLinePtr(y);
        for (int x = 0; x < sz.cx; x++) {
            m_image.push_back(*q++);
            m_image.push_back(*q++);
            m_image.push_back(*q++);
        }
    }
    
    InitNetwork();
    Learn();
    BuildIndex();
    m_processed = true;
    return GetPalette();
}
std::vector<RGBQUAD> NeuQuant::GetPalette() const { 
    std::vector<int> index(m_ColorCount);   
    std::vector<RGBQUAD> palette(m_ColorCount);

    for (int i = m_ColorCount; i-->0;) index[m_network[i].paletteIndex] = i;
    for (int i = m_ColorCount; i-->0;) {
        const Neuron& n = m_network[index[i]];
        palette[i].rgbBlue  = BYTE(max(0, min(255, n.b + 0.5f)));
        palette[i].rgbGreen = BYTE(max(0, min(255, n.g + 0.5f)));
        palette[i].rgbRed   = BYTE(max(0, min(255, n.r + 0.5f)));
    }	
    return palette; 
}

int NeuQuant::QuantizeColor(BYTE r, BYTE g, BYTE b) const {
    if (!m_processed) return -1;

    int best = -1;
    float bestDist = FLT_MAX;
    int i = m_netIndex[g];
    int j = i - 1;
    while (i < m_ColorCount || j >= 0) {
        if (i < m_ColorCount) {
            const Neuron& n = m_network[i];
            float dist = n.g - g;
            if (dist >= bestDist) i = m_ColorCount;
            else {
                ++i;
                dist = fabs(dist) + fabs(n.b - b);
                if (dist < bestDist) {
                    dist += fabs(n.r - r);
                    if (dist < bestDist) {
                        bestDist = dist;
                        best = n.paletteIndex;
                    }
                }
            }
        }
        if (j >= 0) {
            const Neuron& n = m_network[j];
            float dist = g - n.g;
            if (dist >= bestDist) j = -1;
            else {
                --j;
                dist = fabs(dist) + fabs(n.b - b);
                if (dist < bestDist) {
                    dist += fabs(n.r - r);
                    if (dist < bestDist) {
                        bestDist = dist;
                        best = n.paletteIndex;
                    }
                }
            }
        }
    }
    return best;
}
void NeuQuant::InitNetwork() {
    for (int i = m_ColorCount; i-->0;) {
    	float v = 255.0F * i / (m_ColorCount - 1);
        m_network[i].b = v;
        m_network[i].g = v;
        m_network[i].r = v;
        m_network[i].paletteIndex = i;
        m_frequency[i] = 1.0f / m_ColorCount;
        m_bias[i] = 0.0f;
    }
    m_processed = false;
}
void NeuQuant::Learn() {
    const int length = int(m_image.size());
    int factor = m_sampleFactor;
    float alpha = INITIAL_ALPHA;
    float radius = float(m_ColorCount>>3);
    int rad = int(radius);

    if (length < MIN_PICTURE_BYTES) factor = 1;
    int alphaDecay = 30 + (factor - 1) / 3;
    int nSamples = length / (3 * factor);
    int delta = nSamples / CYCLE_COUNT;
    if (delta == 0) delta = 1;

    if (rad <= 1) rad = 0;
    for (int k = 0; k < rad; ++k)
        m_radiusPower[k] = alpha * (1.0f - float(k * k) / (rad * rad));

    int step = 3;
    if (length < MIN_PICTURE_BYTES) step = 3;
    else if (length % PRIME1 != 0)  step = 3 * PRIME1;
    else if (length % PRIME2 != 0)  step = 3 * PRIME2;
    else if (length % PRIME3 != 0)  step = 3 * PRIME3;
    else                            step = 3 * PRIME4;

    for (int count = 0, pixel = 0; count < nSamples; ++count) {
        const int b = m_image[pixel + 0];
        const int g = m_image[pixel + 1];
        const int r = m_image[pixel + 2];
        const int winner = Contest(b, g, r);
        AlterSingle(alpha, winner, b, g, r);
        if (rad != 0) AlterNeighbours(rad, winner, b, g, r);

        pixel += step;
        if (pixel >= length) pixel -= length;

        if ((count + 1) % delta == 0) {
            alpha -= alpha / alphaDecay;
            radius -= radius / 30.0f;
            rad = int(radius);
            if (rad <= 1) rad = 0;
            for (int j = 0; j < rad; ++j)
                m_radiusPower[j] = alpha * (1.0f - float(j * j) / (rad * rad));
        }
    }
}
int NeuQuant::Contest(int b, int g, int r) {
    int bestPos = -1;
    int bestBiasedPos = -1;
    float bestDist = FLT_MAX;
    float bestBiasedDist = FLT_MAX;
    for (int i = 0; i < m_ColorCount; ++i) {
        const Neuron& n = m_network[i];
        const float dist = fabs(n.b - b) + fabs(n.g - g) + fabs(n.r - r);
        const float biasedDist = dist - m_bias[i];
        if (dist < bestDist) {
            bestDist = dist;
            bestPos = i;
        }
        if (biasedDist < bestBiasedDist) {
            bestBiasedDist = biasedDist;
            bestBiasedPos = i;
        }
        const float freqChange = BETA * m_frequency[i];
        m_frequency[i] -= freqChange;
        m_bias[i] += GAMMA * freqChange;
    }
    m_frequency[bestPos] += BETA;
    m_bias[bestPos] -= BETA * GAMMA;
    return bestBiasedPos;
}
void NeuQuant::AlterSingle(float alpha, int index, int b, int g, int r) {
    Neuron& n = m_network[index];
    n.b += alpha * (float(b) - n.b);
    n.g += alpha * (float(g) - n.g);
    n.r += alpha * (float(r) - n.r);
}
void NeuQuant::AlterNeighbours(int radius, int index, int b, int g, int r) {
    int lo = max(-1, index - radius);
    int hi = min(m_ColorCount, index + radius);
    int j = index + 1;
    int k = index - 1;
    int m = 1;
    while (j < hi || k > lo) {
        if (m >= m_radiusPower.size()) break;
        const float amount = m_radiusPower[m++];
        if (j < hi) {
            Neuron& n = m_network[j++];
            n.b += amount * (b - n.b);
            n.g += amount * (g - n.g);
            n.r += amount * (r - n.r);
        }
        if (k > lo) {
            Neuron& n = m_network[k--];
            n.b += amount * (b - n.b);
            n.g += amount * (g - n.g);
            n.r += amount * (r - n.r);
        }
    }
}
// 어느 색을 기준으로 해도 무방;
void NeuQuant::BuildIndex() {
    int prevColor = 0, startPos = 0;
    for (int i = 0; i < m_ColorCount; ++i) {
        int smallestPos = i;
        float smallestGreen = m_network[i].g;
        for (int j = i + 1; j < m_ColorCount; ++j) {
            if (m_network[j].g < smallestGreen) {
                smallestPos = j;
                smallestGreen = m_network[j].g;
            }
        }
        if (smallestPos != i) {
            const Neuron temp = m_network[i];
            m_network[i] = m_network[smallestPos];
            m_network[smallestPos] = temp;
        }
        const int green = max(0, min(255, int(m_network[i].g)));
        if (green != prevColor) {
            m_netIndex[prevColor] = (startPos + i) >> 1;
            for (int j = prevColor + 1; j < green; ++j) m_netIndex[j] = i;
            prevColor = green;
            startPos = i;
        }
    }
    m_netIndex[prevColor] = (startPos + m_ColorCount - 1) >> 1;
    for (int i = prevColor + 1; i < 256; ++i) m_netIndex[i] = m_ColorCount - 1;
}
CRaster NeuQuantizeTest(const CRaster& raster, int nColors, int sampleFactor) {
    CRaster out;
    NeuQuant nq(nColors, sampleFactor);
    const std::vector<RGBQUAD> palette = nq.ProcessImage(raster);
    CSize sz = raster.GetSize();
    out.SetDimensions(sz, 24);
    for (int y = 0; y < sz.cy; ++y) {
        BYTE *p = (BYTE *)raster.GetLinePtr(y);
        BYTE *q = (BYTE *)out.GetLinePtr(y); 
        for (int x = 0; x < sz.cx; ++x) {
            int idx = nq.QuantizeColor(p[2], p[1], p[0]);
            *q++ = palette[idx].rgbBlue;
            *q++ = palette[idx].rgbGreen;
            *q++ = palette[idx].rgbRed;
            p += 3;
        }
    }
    return out;
}

'Image Recognition' 카테고리의 다른 글

PNN Color Quantization  (0) 2026.09.24
Wu Color Quantization  (0) 2026.09.19
Marching Squares  (0) 2026.09.16
K-Means Color Quantization  (0) 2026.09.11
Octree Color Quantization 구현  (0) 2026.09.01
,