diff --git a/cimage.cpp b/cimage.cpp index fe8fa18..0031ca2 100644 --- a/cimage.cpp +++ b/cimage.cpp @@ -5,6 +5,9 @@ #include "cimage.h" +#include "opencv2/imgproc/types_c.h" +#include "opencv2/imgproc.hpp" + #include #include @@ -121,10 +124,246 @@ void cImage::detectSpecialCamera(const LibRaw & iProcessor) if (QString(iProcessor.imgdata.idata.model).contains("IQ260")) m_camType = camera_iiq; - else if (QString(iProcessor.imgdata.idata.make).compare("Canon", Qt::CaseInsensitive)) + else if (!QString(iProcessor.imgdata.idata.make).compare("Canon", Qt::CaseInsensitive)) m_camType = camera_canon; } +cv::Mat cImage::demosaic(LibRaw & iProcessor) +{ + cv::Mat rawMat = cv::Mat(iProcessor.imgdata.sizes.height, iProcessor.imgdata.sizes.width, CV_16UC1); + double dynamicRange = static_cast(iProcessor.imgdata.color.maximum - iProcessor.imgdata.color.black); + + // normalize all image values + for(int rIdx = 0; rIdx < rawMat.rows; rIdx++) + { + unsigned short *ptrRaw = rawMat.ptr(rIdx); + + for (int cIdx = 0; cIdx < rawMat.cols; cIdx++) + { + int colIdx = iProcessor.COLOR(rIdx, cIdx); + double val = static_cast(iProcessor.imgdata.image[(rawMat.cols*rIdx) + cIdx][colIdx]); + + // normalize the value w.r.t the black point defined + val = (val - iProcessor.imgdata.color.black) / dynamicRange; + ptrRaw[cIdx] = clip(val * USHRT_MAX); // for conversion to 16U + } + } + + // no demosaicing + if(m_isChromatic) + { + unsigned long type = static_cast(iProcessor.imgdata.idata.filters); + type = type & 255; + + cv::Mat rgbImg; + + //define bayer pattern + if(type == 180) + cvtColor(rawMat, rgbImg, CV_BayerBG2RGB); //bitmask 10 11 01 00 -> 3(G) 2(B) 1(G) 0(R) -> RG RG RG + else if(type == 30) + cvtColor(rawMat, rgbImg, CV_BayerRG2RGB); //bitmask 00 01 11 10 -> 0 1 3 2 + else if(type == 225) + cvtColor(rawMat, rgbImg, CV_BayerGB2RGB); //bitmask 11 10 00 01 + else if(type == 75) + cvtColor(rawMat, rgbImg, CV_BayerGR2RGB); //bitmask 01 00 10 11 + else + { + qWarning() << "Wrong Bayer Pattern (not BG, RG, GB, GR)\n"; + return cv::Mat(); + } + + rawMat = rgbImg; + } + + // 16U (1 or 3 channeled) Mat + return(rawMat); +} + +cv::Mat cImage::prepareImg(const LibRaw & iProcessor) +{ + cv::Mat rawMat = cv::Mat(iProcessor.imgdata.sizes.height, iProcessor.imgdata.sizes.width, CV_16UC3, cv::Scalar(0)); + double dynamicRange = static_cast(iProcessor.imgdata.color.maximum - iProcessor.imgdata.color.black); + + // normalization function + auto normalize = [&](double val) + { + val = (val - iProcessor.imgdata.color.black) / dynamicRange; + return clip(val * USHRT_MAX); + }; + + for (int rIdx = 0; rIdx < rawMat.rows; rIdx++) + { + unsigned short *ptrI = rawMat.ptr(rIdx); + + for (int cIdx = 0; cIdx < rawMat.cols; cIdx++) + { + + *ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][0]); + ptrI++; + *ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][1]); + ptrI++; + *ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][2]); + ptrI++; + } + } + + return rawMat; +} + +void cImage::whiteBalance(const LibRaw & iProcessor, cv::Mat & img) +{ + // white balance must not be empty at this point + cv::Mat wb = whiteMultipliers(iProcessor); + const float* wbp = wb.ptr(); + assert(wb.cols == 4); + + for(int rIdx = 0; rIdx < img.rows; rIdx++) + { + unsigned short *ptr = img.ptr(rIdx); + + for (int cIdx = 0; cIdx < img.cols; cIdx++) + { + //apply white balance correction + unsigned short r = clip(*ptr * wbp[0]); + unsigned short g = clip(*(ptr+1) * wbp[1]); + unsigned short b = clip(*(ptr+2) * wbp[2]); + + //apply color correction + int cr = qRound(iProcessor.imgdata.color.rgb_cam[0][0] * r + + iProcessor.imgdata.color.rgb_cam[0][1] * g + + iProcessor.imgdata.color.rgb_cam[0][2] * b); + int cg = qRound(iProcessor.imgdata.color.rgb_cam[1][0] * r + + iProcessor.imgdata.color.rgb_cam[1][1] * g + + iProcessor.imgdata.color.rgb_cam[1][2] * b); + int cb = qRound(iProcessor.imgdata.color.rgb_cam[2][0] * r + + iProcessor.imgdata.color.rgb_cam[2][1] * g + + iProcessor.imgdata.color.rgb_cam[2][2] * b); + + // clip & save color corrected values + *ptr = clip(cr); + ptr++; + *ptr = clip(cg); + ptr++; + *ptr = clip(cb); + ptr++; + } + } +} + +cv::Mat cImage::whiteMultipliers(const LibRaw & iProcessor) +{ + // get camera white balance multipliers + cv::Mat wm(1, 4, CV_32FC1); + + float* wmp = wm.ptr(); + + for(int idx = 0; idx < wm.cols; idx++) + wmp[idx] = iProcessor.imgdata.color.cam_mul[idx]; + + if(wmp[3] == 0.0f) + wmp[3] = wmp[1]; // take green (usually its RGBG) + + // normalize white balance multipliers + float w = static_cast(cv::sum(wm)[0] / 4.0f); + float maxW = 1.0f; + + //clipping according the camera model + //if w > 2.0 maxW is 256, otherwise 512 + //tested empirically + //check if it can be defined by some metadata settings? + if(w > 2.0f) + maxW = 255.0f; + if(w > 2.0f && m_camType == camera_canon) + maxW = 511.0f; // some cameras would even need ~800 - why? + + //normalize white point + wm /= maxW; + + // 1 x 4 32FC1 white balance vector + return wm; +} + +void cImage::gammaCorrection(const LibRaw & iProcessor, cv::Mat& img) +{ + // white balance must not be empty at this point + cv::Mat gt = gammaTable(iProcessor); + const unsigned short* gammaLookup = gt.ptr(); + assert(gt.cols == USHRT_MAX); + + for(int rIdx = 0; rIdx < img.rows; rIdx++) + { + unsigned short *ptr = img.ptr(rIdx); + + for(int cIdx = 0; cIdx < img.cols * img.channels(); cIdx++) + { + // values close to 0 are treated linear + if (ptr[cIdx] <= 5) // 0.018 * 255 + ptr[cIdx] = static_cast(qRound(ptr[cIdx] * static_cast(iProcessor.imgdata.params.gamm[1]) / 255.0)); + else + ptr[cIdx] = gammaLookup[ptr[cIdx]]; + } + } + +} + +cv::Mat cImage::gammaTable(const LibRaw & iProcessor) +{ + // OK this is an instance of reverse engineering: + // we found out that the values of (at least) the PhaseOne's achromatic back have to be doubled + // our images are no close to what their software (Capture One does) - only the gamma correction + // seems to be slightly different... -> now we can load compressed IIQs that are not supported by PS : ) + double cameraHackMlp = (QString(iProcessor.imgdata.idata.model) == "IQ260 Achromatic") ? 2.0 : 1.0; + + //read gamma value and create gamma table + double gamma = static_cast(iProcessor.imgdata.params.gamm[0]); + + cv::Mat gmt(1, USHRT_MAX, CV_16UC1); + unsigned short* gmtp = gmt.ptr(); + + for (int idx = 0; idx < gmt.cols; idx++) { + gmtp[idx] = clip(qRound((1.099*std::pow(static_cast(idx) / USHRT_MAX, gamma) - 0.099) * 255 * cameraHackMlp)); + } + + // a 1 x 65535 U16 gamma table + return gmt; +} + +QImage cImage::raw2Img(const LibRaw & iProcessor, cv::Mat & img) +{ + //check the pixel aspect ratio of the raw image + if(iProcessor.imgdata.sizes.pixel_aspect != 1.0f) + cv::resize(img, img, cv::Size(), static_cast(iProcessor.imgdata.sizes.pixel_aspect), 1.0f); + + // revert back to 8-bit image + img.convertTo(img, CV_8U); + + // TODO: for now - fix this! + if(img.channels() == 1) + cv::cvtColor(img, img, CV_GRAY2RGB); + + return(mat2QImage(img)); +} + +QImage cImage::mat2QImage(cv::Mat img) +{ + QImage qImg; + + // since Mat header is copied, a new buffer should be allocated (check this!) + if(img.depth() == CV_32F) + img.convertTo(img, CV_8U, 255); + + if(img.type() == CV_8UC1) + qImg = QImage(img.data, static_cast(img.cols), static_cast(img.rows), static_cast(img.step), QImage::Format_Indexed8); // opencv uses size_t for scaling in x64 applications + if(img.type() == CV_8UC3) + qImg = QImage(img.data, static_cast(img.cols), static_cast(img.rows), static_cast(img.step), QImage::Format_RGB888); + if(img.type() == CV_8UC4) + qImg = QImage(img.data, static_cast(img.cols), static_cast(img.rows), static_cast(img.step), QImage::Format_ARGB32); + + qImg = qImg.copy(); + + return qImg; +} + bool cImage::loadRAW(const QString &fileName) { QSharedPointer ba; @@ -138,45 +377,34 @@ bool cImage::loadRAW(const QString &fileName) detectSpecialCamera(iProcessor); - int error = iProcessor.unpack(); + int error = iProcessor.unpack(); if(strcmp(iProcessor.version(), "0.13.5") != 0) // fixes a bug specific to libraw 13 - version call is UNTESTED iProcessor.raw2image(); if(error != LIBRAW_SUCCESS) return(false); -// cv::Mat rawMat; + cv::Mat rawMat; -// LibRaw RawProcessor; -// QImage image; + if(iProcessor.imgdata.idata.filters) + rawMat = demosaic(iProcessor); + else + rawMat = prepareImg(iProcessor); -// RawProcessor.imgdata.params.gamm[0] = 1/2.4; -// RawProcessor.imgdata.params.gamm[1] = 12.92; -// RawProcessor.imgdata.params.use_camera_wb = 1; + // color correction + white balance + if(m_isChromatic) + whiteBalance(iProcessor, rawMat); -// if(LIBRAW_SUCCESS == RawProcessor.open_file(fileName.toUtf8())) -// { -// if(LIBRAW_SUCCESS == RawProcessor.unpack()) -// { -// if(LIBRAW_SUCCESS == RawProcessor.dcraw_process()) -// { -// libraw_processed_image_t* output = RawProcessor.dcraw_make_mem_image(); + gammaCorrection(iProcessor, rawMat); -// if(LIBRAW_IMAGE_JPEG == output->type) -// { -// image.loadFromData(static_cast(output->data), static_cast(output->data_size), "JPEG"); -// LibRawImagePerformFlip(RawProcessor.imgdata.sizes.flip, image); -// } -// else if(LIBRAW_IMAGE_BITMAP == output->type) -// { -// image = LibRawImageToQImage(static_cast(output->data), output->width, output->height, output->colors, output->bits); -// } // else: could not read -// LibRaw::dcraw_clear_mem(output); -// } -// RawProcessor.recycle(); -// } -// } -// *this = image; + // reduce color noise +// if (DkSettingsManager::param().resources().filterRawImages && mIsChromatic) +// reduceColorNoise(iProcessor, rawMat); + + *this = raw2Img(iProcessor, rawMat); + + iProcessor.recycle(); + rawMat.release(); return(true); } diff --git a/cimage.h b/cimage.h index ec33e0f..d5c8a2d 100644 --- a/cimage.h +++ b/cimage.h @@ -8,6 +8,7 @@ #include "libraw/libraw.h" +#include #include @@ -37,6 +38,51 @@ protected: camera_end }; + template + num clip(float val) const + { + int vr = qRound(val); + + // trust me I'm an engineer @ -2 + // with -2 we do not get pink in oversaturated areas + if (vr > std::numeric_limits::max()) + vr = std::numeric_limits::max()-2; + if (vr < 0) + vr = 0; + + return static_cast(vr); + } + + template + num clip(double val) const + { + int vr = qRound(val); + + // trust me I'm an engineer @ -2 + // with -2 we do not get pink in oversaturated areas + if (vr > std::numeric_limits::max()) + vr = std::numeric_limits::max()-2; + if (vr < 0) + vr = 0; + + return static_cast(vr); + } + + template + num clip(int val) const + { + int vr = qRound(static_cast(val)); + + // trust me I'm an engineer @ -2 + // with -2 we do not get pink in oversaturated areas + if (vr > std::numeric_limits::max()) + vr = std::numeric_limits::max()-2; + if (vr < 0) + vr = 0; + + return static_cast(vr); + } + private: bool m_isChromatic; Cam m_camType; @@ -45,6 +91,16 @@ private: bool openBuffer(const QString &fileName, const QSharedPointer& ba, LibRaw& iProcessor); void detectSpecialCamera(const LibRaw & iProcessor); + + cv::Mat demosaic(LibRaw & iProcessor); + cv::Mat prepareImg(const LibRaw & iProcessor); + void whiteBalance(const LibRaw & iProcessor, cv::Mat & img); + cv::Mat whiteMultipliers(const LibRaw & iProcessor); + void gammaCorrection(const LibRaw & iProcessor, cv::Mat& img); + cv::Mat gammaTable(const LibRaw & iProcessor); + + QImage raw2Img(const LibRaw & iProcessor, cv::Mat & img); + QImage mat2QImage(cv::Mat img); }; #endif // CIMAGE_H diff --git a/pictureConvert.pro b/pictureConvert.pro index 5c586b6..c6a1ac7 100644 --- a/pictureConvert.pro +++ b/pictureConvert.pro @@ -20,8 +20,8 @@ win32-msvc* { win32-g++ { message("mingw") - INCLUDEPATH += C:\dev\3rdParty\exiv2\include C:\dev\3rdParty\libraw C:\dev\3rdParty\libjpeg\include dng - LIBS += -LC:\dev\3rdParty\exiv2\lib -lexiv2.dll -LC:\dev\3rdParty\libraw\lib -lraw -lws2_32 -lz + INCLUDEPATH += C:\dev\3rdParty\exiv2\include C:\dev\3rdParty\libraw C:\dev\3rdParty\libjpeg\include C:\dev\3rdParty\opencv\include dng + LIBS += -LC:\dev\3rdParty\exiv2\lib -lexiv2.dll -LC:\dev\3rdParty\libraw\lib -LC:\dev\3rdParty\opencv\x64\mingw\lib -lraw -lws2_32 -lz -lopencv_core412.dll -lopencv_imgproc412.dll } unix {