Не удалось отредактировать изображение цветового пространства RGB с преобразованием HSL - PullRequest
1 голос
/ 10 октября 2019

Я создаю приложение для редактирования цветового пространства HSL изображения через opencv2 и некоторый код преобразования из Интернета.

Я полагаю, что цветовое пространство исходного изображения - RGB, поэтому я подумала:

  1. Преобразование UIImage в cvMat
  2. Преобразование цветового пространства из BGR в HLS.
  3. Цикл по всем точкам пикселей для получения соответствующих значений HLS.
  4. Пользовательские алгоритмы.
  5. Переписать изменения значения HLS в cvMat
  6. Конвертировать cvMat в UIImage

Вот мой код:

Преобразование между UIImage и cvMat

Ссылка: https://stackoverflow.com/a/10254561/1677041

#import <UIKit/UIKit.h>
#import <opencv2/core/core.hpp>

UIImage *UIImageFromCVMat(cv ::Mat cvMat)
{
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize() * cvMat.total()];

    CGColorSpaceRef colorSpace;
    CGBitmapInfo bitmapInfo;

    if (cvMat.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
        bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
#if 0
        // OpenCV defaults to either BGR or ABGR. In CoreGraphics land,
        // this means using the "32Little" byte order, and potentially
        // skipping the first pixel. These may need to be adjusted if the
        // input matrix uses a different pixel format.
        bitmapInfo = kCGBitmapByteOrder32Little | (
            cvMat.elemSize() == 3? kCGImageAlphaNone : kCGImageAlphaNoneSkipFirst
        );
#else
        bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
#endif
    }

    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(
        cvMat.cols,                 // width
        cvMat.rows,                 // height
        8,                          // bits per component
        8 * cvMat.elemSize(),       // bits per pixel
        cvMat.step[0],              // bytesPerRow
        colorSpace,                 // colorspace
        bitmapInfo,                 // bitmap info
        provider,                   // CGDataProviderRef
        NULL,                       // decode
        false,                      // should interpolate
        kCGRenderingIntentDefault   // intent
    );

    // Getting UIImage from CGImage
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);

    return finalImage;
}

cv::Mat cvMatWithImage(UIImage *image)
{
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    size_t numberOfComponents = CGColorSpaceGetNumberOfComponents(colorSpace);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;

    cv::Mat cvMat(rows, cols, CV_8UC4);  // 8 bits per component, 4 channels
    CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault;

    // check whether the UIImage is greyscale already
    if (numberOfComponents == 1) {
        cvMat = cv::Mat(rows, cols, CV_8UC1);  // 8 bits per component, 1 channels
        bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    }

    CGContextRef contextRef = CGBitmapContextCreate(
        cvMat.data,         // Pointer to backing data
        cols,               // Width of bitmap
        rows,               // Height of bitmap
        8,                  // Bits per component
        cvMat.step[0],      // Bytes per row
        colorSpace,         // Colorspace
        bitmapInfo          // Bitmap info flags
    );

    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);

    return cvMat;
}

Я протестировал только эти две функции и подтвердил, что они работают.

Основные операции по преобразованию:

/// Generate a new image based on specified HSL value changes.
/// @param h_delta h value in [-360, 360]
/// @param s_delta s value in [-100, 100]
/// @param l_delta l value in [-100, 100]
- (void)adjustImageWithH:(CGFloat)h_delta S:(CGFloat)s_delta L:(CGFloat)l_delta completion:(void (^)(UIImage *resultImage))completion
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        Mat original = cvMatWithImage(self.originalImage);
        Mat image;

        cvtColor(original, image, COLOR_BGR2HLS);
        // https://docs.opencv.org/2.4/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html#the-efficient-way

        // accept only char type matrices
        CV_Assert(image.depth() == CV_8U);

        int channels = image.channels();

        int nRows = image.rows;
        int nCols = image.cols * channels;

        int y, x;

        for (y = 0; y < nRows; ++y) {
            for (x = 0; x < nCols; ++x) {
                // https://answers.opencv.org/question/30547/need-to-know-the-hsv-value/
                // https://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html?#cvtcolor
                Vec3b hls = original.at<Vec3b>(y, x);
                uchar h = hls.val[0], l = hls.val[1], s = hls.val[2];

//              h = MAX(0, MIN(360, h + h_delta));
//              s = MAX(0, MIN(100, s + s_delta));
//              l = MAX(0, MIN(100, l + l_delta));

                 printf("(%02d, %02d):\tHSL(%d, %d, %d)\n", x, y, h, s, l); // <= Label 1

                 original.at<Vec3b>(y, x)[0] = h;
                 original.at<Vec3b>(y, x)[1] = l;
                 original.at<Vec3b>(y, x)[2] = s;
            }
        }

        cvtColor(image, image, COLOR_HLS2BGR);
        UIImage *resultImage = UIImageFromCVMat(image);

        dispatch_async(dispatch_get_main_queue(), ^ {
            if (completion) {
                completion(resultImage);
            }
        });
    });
}

Вопрос:

  1. Почему значения HLS выходят за пределы моего ожидаемого диапазона? Это показывает как [0, 255] как диапазон RGB, это неправильное использование cvtColor?
  2. Должен ли я использовать Vec3b в двух циклах for? или Vec3i вместо этого?
  3. В моей мысли что-то не так?

Обновление :

Vec3b hls = original.at<Vec3b>(y, x);
uchar h = hls.val[0], l = hls.val[1], s = hls.val[2];

// Remap the hls value range to human-readable range (0~360, 0~1.0, 0~1.0).
// https://docs.opencv.org/master/de/d25/imgproc_color_conversions.html
float fh, fl, fs;
fh = h * 2.0;
fl = l / 255.0;
fs = s / 255.0;

fh = MAX(0, MIN(360, fh + h_delta));
fl = MAX(0, MIN(1, fl + l_delta / 100));
fs = MAX(0, MIN(1, fs + s_delta / 100));

// Convert them back
fh /= 2.0;
fl *= 255.0;
fs *= 255.0;

printf("(%02d, %02d):\tHSL(%d, %d, %d)\tHSL2(%.4f, %.4f, %.4f)\n", x, y, h, s, l, fh, fs, fl);

original.at<Vec3b>(y, x)[0] = short(fh);
original.at<Vec3b>(y, x)[1] = short(fl);
original.at<Vec3b>(y, x)[2] = short(fs);

1 Ответ

0 голосов
/ 10 октября 2019

1) взгляните на это , особенно на часть RGB-> HLS. Если исходное изображение имеет 8 бит, оно будет 0-255, но если вы используете плавающее изображение, оно может иметь разные значения.

8-битные изображения: V ← 255⋅V, S ← 255⋅S, H ← H / 2 (от 0 до 255)

V следуетбыть L, в документации есть опечатка

Вы можете преобразовать изображение RGB / BGR в изображение с плавающей запятой, и тогда у вас будет полное значение. то есть S и L имеют значения от 0 до 1 и H 0-360.

Но вы должны быть осторожны при преобразовании его обратно.

2) Vec3b - это 8-битное изображение без знака (CV_8U) иVec3i - это целые числа (CV_32S). Зная это, это зависит от того, какой тип вашего изображения. Как вы сказали, он идет от 0 до 255, это должны быть 8-битные без знака, которые вы должны использовать Vec3b. Если вы используете другой, он получит 32 бита на пиксель и использует этот размер для вычисления позиции в массиве пикселей ... так что он может дать что-то вроде выхода за границы, ошибки сегментации или случайных проблем.

Если у вас есть вопрос, не стесняйтесь комментировать

...