OpenCV cv :: cvtColor丢弃alpha通道,如何保存alpha数据?

时间:2013-11-26 22:44:16

标签: c++ opencv image-processing alpha hsv

我已经意识到了几件事:

  • OpenCV不会处理cvtColor(RGBAMat, HSVXMat, RGB2HSV, 4)中的Alpha通道,即使它静默接受并忽略了4通道参数和4通道输出矩阵。

  • OpenCV不转换cvtColor中的数据类型,尽管它很乐意接受来自输入的不同数据类型的输出matix而没有警告。

  • 当从SDL_Surfaces创建Mats时,OpenCV的CV_AUTOSTEP作为Mat(int h, int w, enum val DATATYPE, char* data, size_t step)的最后一个参数无法正常工作。它会导致使用surface->w * sizeof(whatever datatype the surface is)补救的段错误。

由于这些认识,我接受了@ypnos的答案,因为它显然是在正确的轨道上。请注意,此页面上的所有代码都不会按原样运行。

void surface_change_sat(SDL_Surface* src_surf, SDL_Surface* dst_surf, float sat_diff) {
    using namespace cv;

    SDL_LockSurface(src_surf);
    Mat src_mat = Mat(src_surf->h, src_surf->w, CV_8UC4);                // Make mat from surf
    memcpy(src_mat.data, src_surf->pixels, src_surf->h * src_surf->w * sizeof(Uint32));
    SDL_UnlockSurface(src_surf);

    Mat src_rgbI = Mat(src_mat.rows, src_mat.cols, CV_8UC3);     // split rgba into rgb and a
    Mat aI_mat = Mat(src_mat.rows, src_mat.cols, CV_8UC1);       // since hsv has no a
    Mat to_ar[] {src_rgbI, aI_mat};
    int from_to[] = { 0,0, 1,1, 2,2, 3,3 };                      // r=0, ... a=3
    mixChannels(&src_mat, 1, to_ar, 2, from_to, 4);

    Mat rgbF_mat = Mat(src_mat.rows, src_mat.cols, CV_32FC3);    // Make ints into floats
    src_rgbI.convertTo(rgbF_mat, CV_32F);

    typedef Vec<float, 3> flt_vec_t;                             // The type of pixel in hsv
    Mat hsv_mat = Mat(src_mat.rows, src_mat.cols, CV_32FC3);
    cvtColor(rgbF_mat, hsv_mat, CV_RGB2HSV, 3);                  // convert to HSV

    flt_vec_t pix_vec;
    for (MatIterator_<flt_vec_t> mat_it = hsv_mat.begin<flt_vec_t>();
         mat_it != hsv_mat.end<flt_vec_t>();
         mat_it++) {
        pix_vec = *mat_it;
        Matx<float, 3, 1> pix_matx = (Matx<float, 3, 1>)pix_vec;
        CV_Assert(pix_matx.val[1] <= 1.0f && pix_matx.val[1] >= 0.0f);
        pix_matx.val[1] += sat_diff;
        if (pix_matx.val[1] > 1.0f) { pix_matx.val[1] = 1.0f; }
        if (pix_matx.val[1] < 0.0f) { pix_matx.val[1] = 0.0f; }
    }

    cvtColor(hsv_mat, rgbF_mat, CV_HSV2RGB, 3);              // convert back to RGB

    Mat dst_mat = Mat(dst_surf->h, dst_surf->w, CV_8UC4);
    Mat dst_rgbI = Mat(dst_mat.rows, dst_mat.cols, CV_8UC3);
    rgbF_mat.convertTo(dst_rgbI, CV_8U);                     // float back to int
    Mat from_ar[] {dst_rgbI, aI_mat};
    int to_from[] = { 0,0, 1,1, 2,2, 0,3 };                  // r=0, ... a=3
    mixChannels(from_ar, 2, &dst_mat, 1, to_from, 4);        // add alpha for RGBA

    SDL_LockSurface(dst_surf);
    memcpy(dst_surf->pixels, (void*)dst_mat.data, dst_surf->h * dst_surf->w * sizeof(Uint32));
    SDL_UnlockSurface(dst_surf);
}

// -------------------旧问题------------------------ -

在我将CV_Assert添加到以下函数之前,我发现了最神秘的内存错误,并意识到OpenCV正在默默地破坏cv::Mat hsv_mat中的alpha通道。我很困惑如何解决这个问题:一个注释,其他几个相关问题的答案建议使用shell脚本或像imagemagick这样的cli工具。这些在这种特殊情况下没有帮助。我需要一个独立的函数,其依赖性仅限于opencv和标准库。

注意:根据评论中的建议,我明确拆分了Alpha通道,并从Uint8转换为Float32。然而,断言功能现在仍然存在无效内存访问导致段错误的问题。

1 个答案:

答案 0 :(得分:1)

嗯,基本上cvtColor只适用于一组固定的矩阵类型,包括通道的格式和数量。因此,混合和重新混合频道是不可避免的。

我要做的下一件事是使用模板化数据类型。它们可以让您更好地理解矩阵中的内容,但有时OpenCV也会设法违反它(至少在旧版本中)。

我就是这样做的:

SDL_LockSurface(surf);
cv::Mat4b rgba(surf->h, surf->w, surf->pixels);
std::vector<cv::Mat1b> channels_rgba;
cv::split(rgba, channels_rgba);

// create matrix with first three channels only
std::vector<cv::Mat1b> channels_rgb(channels_rgba.begin(),
                                    channels_rgba.begin()+3);
cv::Mat3b rgb;
cv::merge(channels_rgb, rgb);

// create floating point representation
cv::Mat3f rgbhsv;
rgbhsv = rgb; // implicit conversion

// convert to HSV
cv::cvtColor(rgbhsv, rgbhsv, CV_RGB2HSV);

for (cv::Mat3f::iterator mat_it = rgbhsv.begin();
     mat_it != rgbhsv.end(); mat_it++) {
    cv::Vec3f &pix_vec = *mat_it;
    pix_vec[0] += hue_diff;
    if (pix_vec[0] >= 360.0f || pix_vec[0] < 0.0f) {
        pix_vec[0] = (180.0f / M_PI) * (std::asin(std::sin((pix_vec[0] * M_PI / 180.0f))));
    }
}

// convert back to RGB
cv::cvtColor(rgbhsv, rgbhsv, CV_HSV2RGB);

// back to unsigned char channels
rgb = rgbhsv; // implicit conversion
cv::split(rgb, channels_rgb);

// replace first three channels only
for (size_t i = 0; i < channels_rgb.size(); ++i)
    channels_rgba[i] = channels_rgb[i];

cv::merge(channels_rgba, rgba);
SDL_UnlockSurface(surf);

请注意:

  1. 我没有测试此代码!
  2. 可能需要调整数据范围(返回convertTo()而不是赋值运算符!
  3. 我的上一次操作可能不会覆盖表面数据(您可以返回到已知不会分配任何数据的mixChannels)