Bootstrap

C# OpenCV机器视觉:颜色量化法提取主色

在热闹繁华的广告设计圈里,阿强是个怀揣无限创意,却总被颜色搭配折腾得焦头烂额的设计师。每次接到项目,面对那些色彩斑斓的素材图片,他就像迷失在彩色迷宫里的小可怜。

“哎呀呀,这么多颜色,到底哪个才是主色啊?我这眼睛都快要看花了!” 阿强对着电脑屏幕上的图片唉声叹气,头发被他抓得像个鸟窝。他尝试凭感觉挑主色,可结果要么平淡无奇,要么色彩冲突严重,客户的反馈总是不太理想。

直到有一天,阿强在一个技术交流论坛上,偶然发现了 OpenCvSharp 这个神奇的存在,还得知能用颜色量化的方法提取主色。“这不是我的救星来了嘛!” 阿强瞬间来了精神,眼睛瞪得像铜铃,仿佛看到了自己设计事业的转折点。

第一章:神秘 “魔法书” 登场 ——OpenCvSharp 降临

阿强迫不及待一头扎进 OpenCvSharp 的世界,这一探索可不得了,他发现这简直就是一本色彩魔法书,里面藏着无数让色彩乖乖听话的神奇咒语。

“哈哈,OpenCvSharp,你就是我苦寻已久的色彩魔法助手啊!” 阿强兴奋地自言自语,脑海里已经开始幻想自己成为色彩大师,在设计界呼风唤雨的场景。

阿强深入研究后得知,颜色量化就像给图像里的色彩小精灵们重新 “编排队列”。一幅图像往往有海量的颜色,就像一群吵吵闹闹、各自为政的小精灵,颜色量化就是把它们按相似程度分成几个大组,每个组里最具代表性的那个小精灵,就是我们要找的主色啦!这就好比把一群性格各异的小朋友,按兴趣爱好分成几个社团,每个社团的 “小团长” 就是主色。

第二章:准备施展 “魔法”—— 开启奇妙之旅

阿强决定拿一张棘手的广告图片 “开刀”,这张图片色彩复杂得像一团乱麻。他像个即将上战场的战士,在电脑上小心翼翼地安装 OpenCvSharp 库。

“小宝贝,你可要顺顺利利地安装好呀,可别给我捣乱。” 阿强一边点着鼠标,一边小声念叨。但安装过程就像一场惊险刺激的冒险,各种报错和依赖问题像小怪兽一样不断冒出来。阿强一会儿眉头紧皱,一会儿又对着屏幕嘟嘟囔囔,好在他凭借着顽强的毅力和在网上疯狂搜索资料,终于把这些小怪兽一一打败,成功安装好了 OpenCvSharp。

“呼,总算是搞定了!” 阿强长舒一口气,擦了擦额头上的汗珠,脸上露出了胜利的笑容。他迫不及待地打开编程软件,准备大显身手。

第三章:代码冲锋 —— 念起主色提取的 “魔法咒语”

阿强深吸一口气,手指在键盘上飞舞起来,仿佛在编织一张神奇的魔法网。

using System;
using OpenCvSharp;
using System.Collections.Generic;

class MainColorExtractor
{
    static void Main()
    {
        // 1. 读取图像
        Mat image = Cv2.ImRead("tricky_ad_image.jpg");
        if (image.Empty())
        {
            Console.WriteLine("哎呀妈呀,图像读取失败啦!是不是你把图片藏得太深,我找不到啦?快检查检查!");
            return;
        }

        // 2. 将图像转换为HSV颜色空间,HSV对颜色的感知更符合人类直觉
        Mat hsvImage = new Mat();
        Cv2.CvtColor(image, hsvImage, ColorConversionCodes.BGR2HSV);

        // 3. 颜色量化,这里简单地将每个通道量化为4个等级
        int quantizationLevels = 4;
        Mat quantizedImage = new Mat();
        QuantizeImage(hsvImage, quantizationLevels, quantizedImage);

        // 4. 统计量化后每种颜色的出现频率
        Dictionary<Vec3b, int> colorFrequency = new Dictionary<Vec3b, int>();
        for (int i = 0; i < quantizedImage.Rows; i++)
        {
            for (int j = 0; j < quantizedImage.Cols; j++)
            {
                Vec3b color = quantizedImage.At<Vec3b>(i, j);
                if (colorFrequency.ContainsKey(color))
                {
                    colorFrequency[color]++;
                }
                else
                {
                    colorFrequency[color] = 1;
                }
            }
        }

        // 5. 找出频率最高的前几种颜色作为主色,这里取3种
        int numDominantColors = 3;
        List<Vec3b> dominantColors = GetDominantColors(colorFrequency, numDominantColors);

        // 6. 显示结果
        ShowDominantColors(dominantColors);
    }

    static void QuantizeImage(Mat inputImage, int levels, Mat outputImage)
    {
        inputImage.ConvertTo(outputImage, MatType.CV_32F);
        for (int i = 0; i < outputImage.Rows; i++)
        {
            for (int j = 0; j < outputImage.Cols; j++)
            {
                Vec3f color = outputImage.At<Vec3f>(i, j);
                color[0] = (float)Math.Floor(color[0] * levels / 180) * 180 / levels;
                color[1] = (float)Math.Floor(color[1] * levels / 255) * 255 / levels;
                color[2] = (float)Math.Floor(color[2] * levels / 255) * 255 / levels;
                outputImage.Set<Vec3f>(i, j, color);
            }
        }
        outputImage.ConvertTo(outputImage, MatType.CV_8U);
    }

    static List<Vec3b> GetDominantColors(Dictionary<Vec3b, int> colorFrequency, int numColors)
    {
        var sortedColors = new SortedDictionary<int, Vec3b>(Comparer<int>.Create((x, y) => y.CompareTo(x)));
        foreach (var entry in colorFrequency)
        {
            sortedColors[entry.Value] = entry.Key;
        }

        List<Vec3b> result = new List<Vec3b>();
        int count = 0;
        foreach (var entry in sortedColors)
        {
            result.Add(entry.Value);
            count++;
            if (count >= numColors)
            {
                break;
            }
        }
        return result;
    }

    static void ShowDominantColors(List<Vec3b> colors)
    {
        // 创建一个新的图像来显示主色
        Mat colorPalette = new Mat(100, colors.Count * 100, MatType.CV_8UC3);
        for (int i = 0; i < colors.Count; i++)
        {
            Mat colorBlock = new Mat(100, 100, MatType.CV_8UC3, colors[i]);
            Mat roi = colorPalette.SubMat(0, 100, i * 100, (i + 1) * 100);
            colorBlock.CopyTo(roi);
        }

        Cv2.ImShow("Dominant Colors", colorPalette);
        Cv2.WaitKey(0);
        Cv2.DestroyAllWindows();
    }
}

代码解析

  1. 图像读取:阿强用Cv2.ImRead方法去 “召唤” 图片,要是图片没 “召唤” 成功,程序就会像个小喇叭一样提醒他。这就好比邀请朋友来参加派对,如果朋友没来,那派对可就没法好好开始啦。
  2. 颜色空间转换:将图像转换为 HSV 颜色空间。HSV 空间就像是一个更懂人类心思的色彩翻译官,它把颜色分成色调(Hue)、饱和度(Saturation)和明度(Value)三个维度,这样我们能更直观地理解和处理颜色,就像把复杂的颜色语言翻译成了通俗易懂的大白话。
  3. 颜色量化:通过QuantizeImage方法进行颜色量化。在 HSV 的每个通道上,把颜色范围划分成quantizationLevels个等级 。这里设定为 4 个等级,就像把一个长长的跑道分成 4 段,每个像素的颜色就会被 “安排” 到对应的段落里。比如,原本连续变化的色调值,会被近似到 4 个固定的色调值之一。这一步大大减少了颜色的种类,把纷繁复杂的颜色世界变得简单有序。
  4. 统计颜色频率:遍历量化后的图像,用一个字典colorFrequency记录每种颜色出现的次数。这就像在班级里统计每个同学的考试成绩,看看哪个分数段的同学最多。
  5. 找出主色:在GetDominantColors方法里,把记录颜色频率的字典按频率从高到低排序,然后取出频率最高的前几种颜色作为主色。这里取 3 种,就像在班级里找出成绩最好的 3 个同学,他们就是主色的代表。
  6. 结果显示:阿强用ShowDominantColors方法把找到的主色展示出来。他创建了一个新的图像,把每个主色都变成一个小方块,整齐地排列在一起,就像一个色彩拼盘,这样他就能一目了然地看到自己辛苦找到的主色啦。

第四章:成功时刻 —— 魔法生效,主色现形

阿强紧张地按下运行键,眼睛死死地盯着屏幕,大气都不敢出。当屏幕上清晰地出现一排鲜艳的主色色块时,阿强一下子从椅子上弹了起来。

“哇塞,成功啦!我就知道我能行!” 阿强兴奋地在办公室里手舞足蹈,像个中了彩票的孩子。

他迫不及待地把这个好消息分享给同事们,同事们都围过来,看着屏幕上的主色,纷纷竖起大拇指。

“阿强,你太牛了吧!这主色找得太准了!”
“是啊是啊,阿强,快教教我们怎么做到的!”

从那以后,阿强凭借这个神奇的主色提取方法,设计作品越来越出色。客户们看到他的设计方案,都赞不绝口,订单像雪花一样飞来。

阿强知道,这只是他在色彩魔法世界的第一步,他还要继续探索 OpenCvSharp 更多的神奇功能。他相信,只要不断学习,自己一定能成为真正的色彩魔法大师,在设计的舞台上大放异彩。

;