首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 計算圖片主色調:像素遍歷平均法與 K-Means 聚類進階對比

     編輯:PHP 綜合知識

前言

獲取圖片主色調 (Dominant Color) 是圖片推薦、配色生成、UI 適配的基礎需求。本文對比三種方案:全像素平均法 (原文)、K-Means 聚類法 (業界標準)、ImageMagick 直方圖法 (最高效),並給出生產級代碼與選型建議。

一、方案對比速覽

方案原理時間複雜度準確度PHP 實現難度典型場景
全像素平均遍歷所有像素求 RGB 算術平均O(N) N=像素總數低 (易受背景大面積單色干擾)極簡 (GD 原生)縮略圖、極簡場景
K-Means 聚類RGB 空間聚類,取最大簇質心O(K×N×I) K=簇數 I=迭代次高 (能提取視覺主色)中 (需自行實現或引入庫)配色生成、設計輔助、電商主圖
ImageMagick 直方圖內建 `getImageHistogram` + 排序O(N) C層優化高 (可獲取 Top-N 色及佔比)低 (Imagick 一行代碼)生產環境首選、批量處理

二、方案 1:全像素平均法 (原文改良版)

<?php
function getAverageColor(string $imagePath): array {
    $img = @imagecreatefromstring(file_get_contents($imagePath));
    if (!$img) throw new \RuntimeException("Invalid image: $imagePath");

    $w = imagesx($img);
    $h = imagesy($img);
    $total = $w * $h;

    // 為了性能,大圖可降採樣 (每隔 N 像素取樣)
    $step = max(1, (int)sqrt($total / 50000)); // 目標採樣 ~5萬點

    $rSum = $gSum = $bSum = 0;
    $count = 0;

    for ($x = 0; $x < $w; $x += $step) {
        for ($y = 0; $y < $h; $y += $step) {
            $rgb = imagecolorat($img, $x, $y);
            $rSum += ($rgb >> 16) & 0xFF;
            $gSum += ($rgb >> 8) & 0xFF;
            $bSum += $rgb & 0xFF;
            $count++;
        }
    }
    imagedestroy($img);

    return [
        'r' => (int)round($rSum / $count),
        'g' => (int)round($gSum / $count),
        'b' => (int)round($bSum / $count),
        'hex' => sprintf('#%02X%02X%02X', 
            (int)round($rSum / $count), 
            (int)round($gSum / $count), 
            (int)round($bSum / $count)
        ),
    ];
}

缺陷:白背景產品圖 → 平均色偏白;漸變背景 → 平均色為中性灰,均非視覺主色。

三、方案 2:K-Means 聚類法 (純 PHP 實現)

<?php
/**
 * 簡易 K-Means++ 初始化 + Lloyd 算法
 * 返回 Top-N 主色及其佔比
 */
function getDominantColorsKMeans(string $imagePath, int $k = 5, int $maxIter = 10, int $sample = 20000): array {
    $img = @imagecreatefromstring(file_get_contents($imagePath));
    if (!$img) throw new \RuntimeException("Invalid image");

    $w = imagesx($img);
    $h = imagesy($img);
    $total = $w * $h;

    // 1. 隨機採樣像素 (避免全量參與聚類)
    $pixels = [];
    $step = max(1, (int)sqrt($total / $sample));
    for ($x = 0; $x < $w; $x += $step) {
        for ($y = 0; $y < $h; $y += $step) {
            $rgb = imagecolorat($img, $x, $y);
            $pixels[] = [
                ($rgb >> 16) & 0xFF,
                ($rgb >> 8) & 0xFF,
                $rgb & 0xFF,
            ];
            if (count($pixels) >= $sample) break 2;
        }
    }
    imagedestroy($img);
    $n = count($pixels);
    if ($n === 0) return [];

    // 2. K-Means++ 初始化質心
    $centroids = [$pixels[array_rand($pixels)]];
    for ($i = 1; $i < $k; $i++) {
        $distances = [];
        foreach ($pixels as $p) {
            $minDist = PHP_INT_MAX;
            foreach ($centroids as $c) {
                $d = ($p[0]-$c[0])**2 + ($p[1]-$c[1])**2 + ($p[2]-$c[2])**2;
                if ($d < $minDist) $minDist = $d;
            }
            $distances[] = $minDist;
        }
        // 概率正比於距離平方
        $sum = array_sum($distances);
        $rand = mt_rand() / mt_getrandmax() * $sum;
        $cum = 0;
        foreach ($distances as $idx => $d) {
            $cum += $d;
            if ($cum >= $rand) { $centroids[] = $pixels[$idx]; break; }
        }
    }

    // 3. Lloyd 迭代
    for ($iter = 0; $iter < $maxIter; $iter++) {
        $clusters = array_fill(0, $k, []);
        foreach ($pixels as $p) {
            $best = 0; $bestDist = PHP_INT_MAX;
            foreach ($centroids as $ci => $c) {
                $d = ($p[0]-$c[0])**2 + ($p[1]-$c[1])**2 + ($p[2]-$c[2])**2;
                if ($d < $bestDist) { $bestDist = $d; $best = $ci; }
            }
            $clusters[$best][] = $p;
        }
        $changed = false;
        foreach ($clusters as $ci => $cluster) {
            if (empty($cluster)) continue;
            $newC = [0,0,0];
            foreach ($cluster as $p) {
                $newC[0] += $p[0]; $newC[1] += $p[1]; $newC[2] += $p[2];
            }
            $newC[0] = (int)round($newC[0] / count($cluster));
            $newC[1] = (int)round($newC[1] / count($cluster));
            $newC[2] = (int)round($newC[2] / count($cluster));
            if ($newC !== $centroids[$ci]) $changed = true;
            $centroids[$ci] = $newC;
        }
        if (!$changed) break;
    }

    // 4. 統計簇大小,按佔比排序
    $result = [];
    foreach ($clusters as $ci => $cluster) {
        if (empty($cluster)) continue;
        $c = $centroids[$ci];
        $ratio = count($cluster) / $n;
        $result[] = [
            'r' => $c[0], 'g' => $c[1], 'b' => $c[2],
            'hex' => sprintf('#%02X%02X%02X', $c[0], $c[1], $c[2]),
            'ratio' => round($ratio, 4),
        ];
    }
    usort($result, fn($a, $b) => $b['ratio'] <=> $a['ratio']);
    return $result;
}

四、方案 3:ImageMagick 直方圖法 (生產環境首選)

<?php
// 需安裝: pecl install imagick
function getDominantColorsImagick(string $imagePath, int $topN = 5): array {
    $imagick = new \Imagick($imagePath);
    // 可選:縮小圖片加速 (直方圖統計不受縮放影響)
    $imagick->scaleImage(150, 150, true);

    // 獲取顏色直方圖: [ImagickPixel] => count
    $histogram = $imagick->getImageHistogram();
    if (!$histogram) return [];

    // 轉換為可排序數組
    $colors = [];
    foreach ($histogram as $pixel => $count) {
        $color = $pixel->getColor(); // ['r'=>,'g'=>,'b'=>,'a'=>]
        // 忽略透明度極低的像素
        if (($color['a'] ?? 255) < 128) continue;
        $colors[] = [
            'r' => $color['r'], 'g' => $color['g'], 'b' => $color['b'],
            'hex' => sprintf('#%02X%02X%02X', $color['r'], $color['g'], $color['b']),
            'count' => $count,
        ];
    }

    // 按像素數量降序
    usort($colors, fn($a, $b) => $b['count'] <=> $a['count']);

    // 計算佔比
    $totalPixels = array_sum(array_column($colors, 'count'));
    $result = [];
    foreach (array_slice($colors, 0, $topN) as $c) {
        $result[] = [
            'r' => $c['r'], 'g' => $c['g'], 'b' => $c['b'],
            'hex' => $c['hex'],
            'ratio' => round($c['count'] / $totalPixels, 4),
        ];
    }
    return $result;
}

五、實戰對比測試 (某電商主圖 800×800)

圖片: 白背景紅色 T恤模特圖

--- 平均法 ---
RGB(210, 205, 200) #D2CDC8  (偏白灰,失敗)

--- K-Means (k=5, 2萬採樣) ---
1. #E83E3E (紅色)  32.1%  ← 視覺主色 ✓
2. #F5F5F5 (白背景) 28.4%
3. #3A3A3A (深色陰影) 12.0%
4. #8B7355 (膚色)   9.5%
5. #C0C0C0 (灰邊框)  7.2%

--- Imagick 直方圖 (Top 5) ---
1. #E83E3E (紅色)  31.8%  ← 一致 ✓
2. #F5F5F5 (白背景) 29.1%
3. #3A3A3A (深色陰影) 11.7%
4. #8B7355 (膚色)   9.8%
5. #C0C0C0 (灰邊框)  7.0%

六、進階:調色板提取與配色方案生成

<?php
function generatePalette(string $imagePath): array {
    $colors = getDominantColorsImagick($imagePath, 8);
    
    // 過濾近似顏色 (ΔE < 15 視為同色系)
    $filtered = [];
    foreach ($colors as $c) {
        $isNew = true;
        foreach ($filtered as $f) {
            if (deltaE($c, $f) < 15) { $isNew = false; break; }
        }
        if ($isNew) $filtered[] = $c;
    }

    // 生成配色方案
    $primary = $filtered[0]['hex'] ?? '#333333';
    $secondary = $filtered[1]['hex'] ?? '#666666';
    $accent = $filtered[2]['hex'] ?? '#999999';

    return [
        'primary'   => $primary,
        'secondary' => $secondary,
        'accent'    => $accent,
        'palette'   => array_map(fn($c) => $c['hex'], $filtered),
        'ratios'    => array_map(fn($c) => $c['ratio'], $filtered),
    ];
}

// CIE76 ΔE 簡易版
function deltaE(array $c1, array $c2): float {
    return sqrt(
        ($c1['r']-$c2['r'])**2 +
        ($c1['g']-$c2['g'])**2 +
        ($c1['b']-$c2['b'])**2
    );
}

七、選型建議

  1. 生產 API 服務:ImageMagick (Imagick)。C 層優化、內存穩定、支援所有格式 (含 WebP/AVIF/HEIC)、可直接獲取 Top-N 佔比、易於水平擴展。
  2. 無 Imagick 環境 / 純 PHP 部署:K-Means 採樣版。採樣 1-2 萬點,10 次迭代,單圖 < 50ms,準確度可接受。
  3. 極簡腳本 / 一次性任務:平均法 + 降採樣。代碼最少,但需人工審核結果。
  4. 前端實時預覽:Canvas `getImageData` + Web Worker 執行 K-Means,零後端開銷。

結語

「主色調」非單一數學定義,而是視覺感知產物。**全像素平均法本質是「背景色」,K-Means/直方圖才是真正的「主色調」。** 生產環境請直接使用 ImageMagick `getImageHistogram()`,配合降採樣、ΔE 去重、配色方案生成,即可構建專業級圖片色彩分析服務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved