You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于指定颜色的Floyd-Steinberg抖动灰度图像生成问题求助

问题说明

我希望基于指定颜色生成Floyd-Steinberg抖动图像,抖动处理完成后,匹配输入颜色的像素设为黑色,其余设为白色(或反之),目标效果参考使用以下RGB色值的示例:(255,255,255)白色、(0,215,225)浅蓝、(0,120,240)中蓝、(0,0,120)深蓝、(0,0,0)黑色。

我尝试复现该效果但未成功,当前实现的效果如下:

  • 浅蓝效果:浅蓝效果
  • 黑色效果:黑色效果

输入图像:输入图像

当前实现代码如下:

#include <opencv2/opencv.hpp>

cv::Vec3b FindClosestPaletteColor_(cv::Vec3b Color, const std::vector<cv::Vec3b>& Palette) {
    int MinID = 0;
    cv::Vec3b Difference = Color - Palette[0];
    float MinDistance = cv::norm(Difference);
    for (size_t i = 1; i < Palette.size(); i++) {
        Difference = Color - Palette[i];
        float Distance = cv::norm(Difference);
        if (Distance < MinDistance) {
            MinDistance = Distance;
            MinID = i;
        }
    }
    return Palette[MinID];
}

cv::Mat FloydSteinbergDithering_(cv::Mat InputImage, const std::vector<cv::Vec3b>& ColorPalette) {
    cv::Mat Image = InputImage.clone();
    cv::Mat ResultImage = Image.clone();
    for (int i = 0; i < Image.rows; i++) {
        for (int j = 0; j < Image.cols; j++) {
            cv::Vec3b CurrentColor = Image.at<cv::Vec3b>(i, j);
            cv::Vec3b NewPixelColor = FindClosestPaletteColor_(CurrentColor, ColorPalette);
            ResultImage.at<cv::Vec3b>(i, j) = NewPixelColor;

            for (int k = 0; k < 3; k++) {
                int QuantError = (int)CurrentColor[k] - NewPixelColor[k];
                if (j + 1 < Image.cols)
                    Image.at<cv::Vec3b>(i, j + 1)[k] = cv::saturate_cast<uchar>(Image.at<cv::Vec3b>(i, j + 1)[k] + (7 * QuantError) / 16);
                if (i + 1 < Image.rows && j - 1 >= 0)
                    Image.at<cv::Vec3b>(i + 1, j - 1)[k] = cv::saturate_cast<uchar>(Image.at<cv::Vec3b>(i + 1, j - 1)[k] + (3 * QuantError) / 16);
                if (i + 1 < Image.rows)
                    Image.at<cv::Vec3b>(i + 1, j)[k] = cv::saturate_cast<uchar>(Image.at<cv::Vec3b>(i + 1, j)[k] + (5 * QuantError) / 16);
                if (i + 1 < Image.rows && j + 1 < Image.cols)
                    Image.at<cv::Vec3b>(i + 1, j + 1)[k] = cv::saturate_cast<uchar>(Image.at<cv::Vec3b>(i + 1, j + 1)[k] + (1 * QuantError) / 16);
            }
        }
    }
    return ResultImage;
}

int main() 
{

    cv::Mat InputImage = cv::imread("TestImage.jpg");
    if (InputImage.empty()) {
        std::cerr << "Error: Unable to load input image!" << std::endl;
        return -1;
    }

    //I have used white as secondary color. But want it to only have 1 color as input in the final version. So it can work with any color
    /*Black*/std::vector<cv::Vec3b> ColorPalette = { cv::Vec3b(0, 0, 0), cv::Vec3b(255, 255, 255) };
///*light Blue*/std::vector<cv::Vec3b> ColorPalette = { cv::Vec3b(225, 215, 0), cv::Vec3b(255, 255, 255) };
    cv::Mat ResultImage = FloydSteinbergDithering_(InputImage, ColorPalette);

    cv::imwrite("output_image.jpg", ResultImage);

    cv::cvtColor(ResultImage, ResultImage, cv::COLOR_BGR2GRAY);
    cv::bitwise_not(ResultImage, ResultImage);

    cv::imwrite("GrayScaledImage.jpg", ResultImage);

    cv::imshow("Result Image", ResultImage);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}
问题分析与修复

当前代码存在三个核心问题:

  1. 颜色空间错误:OpenCV默认用BGR存储图像,设置浅蓝调色板时直接用cv::Vec3b(225,215,0)会导致颜色匹配逻辑混乱,因为目标色是RGB格式的(0,215,225)。
  2. 后处理逻辑偏离需求:先做双色调抖动再转灰度取反,和“匹配指定色的像素设为黑/白”的目标逻辑不符。
  3. 调色板设计不合理:目标是基于单指定色生成抖动再映射到黑白,而非直接用指定色和白色做抖动。

修改后的代码

#include <opencv2/opencv.hpp>
#include <cmath>

// 计算当前像素与目标颜色的相似度(RGB空间)
float ColorSimilarity(const cv::Vec3b& pixel, const cv::Vec3b& targetRGB) {
    // 转换OpenCV的BGR到RGB
    cv::Vec3b pixelRGB(pixel[2], pixel[1], pixel[0]);
    int dr = pixelRGB[0] - targetRGB[0];
    int dg = pixelRGB[1] - targetRGB[1];
    int db = pixelRGB[2] - targetRGB[2];
    // 返回欧氏距离的倒数(值越大越相似)
    return 1.0f / (1.0f + sqrt(dr*dr + dg*dg + db*db));
}

cv::Mat FloydSteinbergDitheringForTargetColor(cv::Mat inputImage, const cv::Vec3b& targetRGB) {
    cv::Mat image = inputImage.clone();
    cv::Mat result = cv::Mat::zeros(image.size(), CV_8UC1); // 最终黑白图

    for (int i = 0; i < image.rows; ++i) {
        for (int j = 0; j < image.cols; ++j) {
            cv::Vec3b currentBGR = image.at<cv::Vec3b>(i, j);
            
            // 计算相似度,判断是否设为黑色(匹配目标色)或白色
            float similarity = ColorSimilarity(currentBGR, targetRGB);
            uchar newPixel = (similarity > 0.5f) ? 0 : 255;
            result.at<uchar>(i, j) = newPixel;

            // 基于灰度亮度计算误差,用于抖动扩散
            cv::Vec3b currentRGB(currentBGR[2], currentBGR[1], currentBGR[0]);
            uchar currentGray = cv::saturate_cast<uchar>(0.299*currentRGB[0] + 0.587*currentRGB[1] + 0.114*currentRGB[2]);
            uchar newGray = newPixel;
            int error = currentGray - newGray;

            // Floyd-Steinberg误差扩散(统一修改三通道为灰度值,简化计算)
            if (j + 1 < image.cols) {
                uchar adjusted = cv::saturate_cast<uchar>(image.at<cv::Vec3b>(i, j+1)[0] + (error * 7) / 16);
                image.at<cv::Vec3b>(i, j+1) = cv::Vec3b(adjusted, adjusted, adjusted);
            }
            if (i + 1 < image.rows && j - 1 >= 0) {
                uchar adjusted = cv::saturate_cast<uchar>(image.at<cv::Vec3b>(i+1, j-1)[0] + (error * 3) / 16);
                image.at<cv::Vec3b>(i+1, j-1) = cv::Vec3b(adjusted, adjusted, adjusted);
            }
            if (i + 1 < image.rows) {
                uchar adjusted = cv::saturate_cast<uchar>(image.at<cv::Vec3b>(i+1, j)[0] + (error * 5) / 16);
                image.at<cv::Vec3b>(i+1, j) = cv::Vec3b(adjusted, adjusted, adjusted);
            }
            if (i + 1 < image.rows && j + 1 < image.cols) {
                uchar adjusted = cv::saturate_cast<uchar>(image.at<cv::Vec3b>(i+1, j+1)[0] + (error * 1) / 16);
                image.at<cv::Vec3b>(i+1, j+1) = cv::Vec3b(adjusted, adjusted, adjusted);
            }
        }
    }

    return result;
}

int main() {
    cv::Mat inputImage = cv::imread("TestImage.jpg");
    if (inputImage.empty()) {
        std::cerr << "Error: Unable to load input image!" << std::endl;
        return -1;
    }

    // 设置目标颜色(RGB格式),比如浅蓝(0,215,225)
    cv::Vec3b targetRGB(0, 215, 225);
    // 或者黑色(0,0,0)
    // cv::Vec3b targetRGB(0, 0, 0);

    cv::Mat resultImage = FloydSteinbergDitheringForTargetColor(inputImage, targetRGB);

    // 保存黑白抖动结果
    cv::imwrite("output_blackwhite_dither.jpg", resultImage);

    // 如需反转效果(匹配色为白,其余为黑),取消注释
    // cv::bitwise_not(resultImage, resultImage);
    // cv::imwrite("output_inverted.jpg", resultImage);

    cv::imshow("Result", resultImage);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

关键修改说明

  1. 颜色空间校正:明确将OpenCV的BGR格式转换为RGB后计算颜色相似度,避免匹配错误。
  2. 单目标色逻辑:直接基于目标色的相似度生成黑白二值图,完全符合“匹配指定色设为黑/白”的需求。
  3. 误差扩散优化:基于灰度亮度计算抖动误差,简化通道操作的同时保证抖动效果自然。
  4. 可切换效果:通过bitwise_not可以快速切换“匹配色为黑”或“匹配色为白”的两种模式。

内容的提问来源于stack exchange,提问作者Smile

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 05:17:33