Reputation: 1987
I have realized several things:
that OpenCV does not handle alpha channels in cvtColor(RGBAMat, HSVXMat, RGB2HSV, 4)
even though it silently accepts and ignores the 4 channel parameter and the 4 channel output matrix.
that OpenCV does not convert datatypes in cvtColor
though it happily accepts an output matix of different datatype from the input without warning.
that OpenCV's CV_AUTOSTEP
as the last parameter to Mat(int h, int w, enum val DATATYPE, char* data, size_t step)
does not work when creating Mats from SDL_Surfaces. It causes segfaults that are remedied by using surface->w * sizeof(whatever datatype the surface is)
.
Because of these realizations, I have accepted @ypnos answer since it is clearly on the right track. Be warned that none of the code on this page works as is.
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);
}
//-------------------Old Question -------------------------
I was getting the most mysterious memory errors until I added the CV_Assert
s to the following function and realized that OpenCV was silently destroying the alpha channel in cv::Mat hsv_mat
. I'm confused as to how to resolve this problem:One note, several other answers to related questions suggested thing like using shell scripts or cli tools like imagemagick. These are not helpful in this particular case. I need a standalone function with dependencies limited to opencv and the standard library.
Note: Following a suggestion in the comments, I explicitly split the alpha channel and converted from Uint8 to Float32. Now the assertions function, however, there is still a problem with invalid memory access causing a segfault.
Upvotes: 2
Views: 5619
Reputation: 52317
Well, basically cvtColor only works with a fixed set of matrix types, including the format and number of channels. So unmixing and remixing the channels is unavoidable.
The next thing I would do is use the templated data types. They give you a better understanding of what is held within a matrix, however sometimes OpenCV manages to violate that, too (at least in older versions).
This is how I would do it:
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);
Please note:
Upvotes: 1