//////////////////////////////////////////////////////////////// // // Algorithm for Pentax/Sony Pixel Shift raw files with motion detection // // Copyright (C) 2016 - 2018 Ingo Weyrich // // // pixelshift.cc is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // //////////////////////////////////////////////////////////////// #include #include #include "array2D.h" #include "gauss.h" #include "median.h" #include "procparams.h" #include "rawimagesource.h" #include "sleef.c" #include "../rtgui/multilangmgr.h" #include "../rtgui/options.h" //#define BENCHMARK #include "StopWatch.h" namespace { float greenDiff(float a, float b, float stddevFactor, float eperIso, float nreadIso, float prnu) { // calculate the difference between two green samples float gDiff = a - b; gDiff *= eperIso; gDiff *= gDiff; float avg = (a + b) * 0.5f; avg *= eperIso; prnu *= avg; float stddev = stddevFactor * (avg + nreadIso + prnu * prnu); return gDiff - stddev; } float nonGreenDiffCross(float right, float left, float top, float bottom, float centre, float clippedVal, float stddevFactor, float eperIso, float nreadIso, float prnu) { if(rtengine::max(right, left, top, bottom, centre) > clippedVal) { return 0.f; } // check non green cross float hDiff = (right + left) * 0.5f - centre; hDiff *= eperIso; hDiff *= hDiff; float vDiff = (top + bottom) * 0.5f - centre; vDiff *= eperIso; vDiff *= vDiff; float avg = ((right + left) + (top + bottom)) * 0.25f; avg *= eperIso; prnu *= avg; float stddev = stddevFactor * (avg + nreadIso + prnu * prnu); return std::min(hDiff, vDiff) - stddev; } void paintMotionMask(int index, bool showMotion, float *maskDest, float *nonMaskDest0, float *nonMaskDest1) { if(showMotion) { // if showMotion is enabled colourize the pixel maskDest[index] = 13500.f; nonMaskDest1[index] = nonMaskDest0[index] = 0.f; } } void invertMask(int xStart, int xEnd, int yStart, int yEnd, const array2D &maskIn, array2D &maskOut) { #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for(int i = yStart; i < yEnd; ++i) { #ifdef _OPENMP #pragma omp simd #endif for(int j = xStart; j < xEnd; ++j) { maskOut[i][j] = ~maskIn[i][j]; } } } void xorMasks(int xStart, int xEnd, int yStart, int yEnd, const array2D &maskIn, array2D &maskOut) { #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for(int i = yStart; i < yEnd; ++i) { #ifdef _OPENMP #pragma omp simd #endif for(int j = xStart; j < xEnd; ++j) { maskOut[i][j] ^= maskIn[i][j]; } } } void floodFill4Impl(int yin, int xin, int xStart, int xEnd, int yStart, int yEnd, array2D &mask, std::stack, std::vector>> &coordStack) { coordStack.emplace(xin, yin); while(!coordStack.empty()) { auto coord = coordStack.top(); coordStack.pop(); auto x = coord.first, y = coord.second; if(mask[y][x] == 255) { auto yUp = y - 1, yDown = y + 1; bool lastXUp = false, lastXDown = false, firstXUp = false, firstXDown = false; mask[y][x] = 0; if(yUp >= yStart && mask[yUp][x] == 255) { coordStack.emplace(x, yUp); firstXUp = lastXUp = true; } if(yDown < yEnd && mask[yDown][x] == 255) { coordStack.emplace(x, yDown); firstXDown = lastXDown = true; } auto xr = x + 1; while(xr < xEnd && mask[y][xr] == 255) { mask[y][xr] = 0; if(yUp >= yStart && mask[yUp][xr] == 255) { if(!lastXUp) { coordStack.emplace(xr, yUp); lastXUp = true; } } else { lastXUp = false; } if(yDown < yEnd && mask[yDown][xr] == 255) { if(!lastXDown) { coordStack.emplace(xr, yDown); lastXDown = true; } } else { lastXDown = false; } xr++; } auto xl = x - 1; lastXUp = firstXUp; lastXDown = firstXDown; while(xl >= xStart && mask[y][xl] == 255) { mask[y][xl] = 0; if(yUp >= yStart && mask[yUp][xl] == 255) { if(!lastXUp) { coordStack.emplace(xl, yUp); lastXUp = true; } } else { lastXUp = false; } if(yDown < yEnd && mask[yDown][xl] == 255) { if(!lastXDown) { coordStack.emplace(xl, yDown); lastXDown = true; } } else { lastXDown = false; } xl--; } mask[y][x] = 0; } } } void floodFill4(int xStart, int xEnd, int yStart, int yEnd, array2D &mask) { #ifdef _OPENMP #pragma omp parallel #endif { std::stack, std::vector>> coordStack; #ifdef _OPENMP #pragma omp for schedule(dynamic,128) nowait #endif for(uint16_t i = yStart; i < yEnd; i++) { floodFill4Impl(i, xStart, xStart, xEnd, yStart, yEnd, mask, coordStack); } #ifdef _OPENMP #pragma omp for schedule(dynamic,128) nowait #endif for(int16_t i = yEnd - 1; i >= 0 ; i--) { floodFill4Impl(i, xEnd - 1, xStart, xEnd, yStart, yEnd, mask, coordStack); } #ifdef _OPENMP #pragma omp sections nowait #endif { #ifdef _OPENMP #pragma omp section #endif { uint16_t i = yStart; for(uint16_t j = xStart; j < xEnd; j++) { floodFill4Impl(i, j, xStart, xEnd, yStart, yEnd, mask, coordStack); } } #ifdef _OPENMP #pragma omp section #endif { uint16_t i = yStart; for(uint16_t j = xEnd - 1; j >= xStart; j--) { floodFill4Impl(i, j, xStart, xEnd, yStart, yEnd, mask, coordStack); } } #ifdef _OPENMP #pragma omp section #endif { uint16_t i = yEnd; for(uint16_t j = xStart; j < xEnd; j++) { floodFill4Impl(i, j, xStart, xEnd, yStart, yEnd, mask, coordStack); } } #ifdef _OPENMP #pragma omp section #endif { uint16_t i = yEnd; for(uint16_t j = xEnd - 1; j >= xStart; j--) { floodFill4Impl(i, j, xStart, xEnd, yStart, yEnd, mask, coordStack); } } } } } void calcFrameBrightnessFactor(unsigned int frame, uint32_t datalen, LUTu *histo[4], float brightnessFactor[4]) { float medians[4]; for(int i = 0; i < 4; ++i) { //find median of histogram uint32_t median = 0, count = 0; while(count < datalen / 2) { count += (*histo[i])[median]; ++median; } const float weight = (count - datalen / 2.f) / (*histo[i])[median - 1]; medians[i] = rtengine::intp(weight, (float)(median - 2), (float)(median - 1)); } for(int i = 0; i < 4; ++i) { brightnessFactor[i] = medians[frame] / medians[i]; } } } using namespace std; using namespace rtengine; void RawImageSource::pixelshift(int winx, int winy, int winw, int winh, const procparams::RAWParams &rawParamsIn, unsigned int frame, const std::string &make, const std::string &model, float rawWpCorrection) { BENCHFUN if(numFrames != 4) { // fallback for non pixelshift files amaze_demosaic_RT(winx, winy, winw, winh, rawData, red, green, blue, options.chunkSizeAMAZE, options.measure); return; } procparams::RAWParams::BayerSensor bayerParams = rawParamsIn.bayersensor; bool motionDetection = true; if(bayerParams.pixelShiftMotionCorrectionMethod == procparams::RAWParams::BayerSensor::PSMotionCorrectionMethod::AUTO) { bool pixelShiftEqualBright = bayerParams.pixelShiftEqualBright; bayerParams.setPixelShiftDefaults(); bayerParams.pixelShiftEqualBright = pixelShiftEqualBright; } else if(bayerParams.pixelShiftMotionCorrectionMethod == procparams::RAWParams::BayerSensor::PSMotionCorrectionMethod::OFF) { motionDetection = false; bayerParams.pixelShiftShowMotion = false; } const bool showMotion = bayerParams.pixelShiftShowMotion; const bool showOnlyMask = bayerParams.pixelShiftShowMotionMaskOnly && showMotion; const float smoothFactor = 1.0 - bayerParams.pixelShiftSmoothFactor; if(motionDetection) { if(!showOnlyMask) { if(bayerParams.pixelShiftMedian) { // We need the demosaiced frames for motion correction if (bayerParams.pixelShiftDemosaicMethod == bayerParams.getPSDemosaicMethodString(procparams::RAWParams::BayerSensor::PSDemosaicMethod::LMMSE)) { lmmse_interpolate_omp(winw, winh, *(rawDataFrames[0]), red, green, blue, bayerParams.lmmse_iterations); } else if (bayerParams.pixelShiftDemosaicMethod == bayerParams.getPSDemosaicMethodString(procparams::RAWParams::BayerSensor::PSDemosaicMethod::AMAZEVNG4)) { dual_demosaic_RT (true, rawParamsIn, winw, winh, *(rawDataFrames[0]), red, green, blue, bayerParams.dualDemosaicContrast, true); } else { amaze_demosaic_RT(winx, winy, winw, winh, *(rawDataFrames[0]), red, green, blue, options.chunkSizeAMAZE, options.measure); } multi_array2D redTmp(winw, winh); multi_array2D greenTmp(winw, winh); multi_array2D blueTmp(winw, winh); for(int i = 0; i < 3; i++) { if (bayerParams.pixelShiftDemosaicMethod == bayerParams.getPSDemosaicMethodString(procparams::RAWParams::BayerSensor::PSDemosaicMethod::LMMSE)) { lmmse_interpolate_omp(winw, winh, *(rawDataFrames[i + 1]), redTmp[i], greenTmp[i], blueTmp[i], bayerParams.lmmse_iterations); } else if (bayerParams.pixelShiftDemosaicMethod == bayerParams.getPSDemosaicMethodString(procparams::RAWParams::BayerSensor::PSDemosaicMethod::AMAZEVNG4)) { dual_demosaic_RT (true, rawParamsIn, winw, winh, *(rawDataFrames[i + 1]), redTmp[i], greenTmp[i], blueTmp[i], bayerParams.dualDemosaicContrast, true); } else { amaze_demosaic_RT(winx, winy, winw, winh, *(rawDataFrames[i + 1]), redTmp[i], greenTmp[i], blueTmp[i], options.chunkSizeAMAZE, options.measure); } } #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for(int i = winy + border; i < winh - border; i++) { for(int j = winx + border; j < winw - border; j++) { red[i][j] = median(red[i][j], redTmp[0][i + 1][j], redTmp[1][i + 1][j + 1], redTmp[2][i][j + 1]); } for(int j = winx + border; j < winw - border; j++) { green[i][j] = median(green[i][j], greenTmp[0][i + 1][j], greenTmp[1][i + 1][j + 1], greenTmp[2][i][j + 1]); } for(int j = winx + border; j < winw - border; j++) { blue[i][j] = median(blue[i][j], blueTmp[0][i + 1][j], blueTmp[1][i + 1][j + 1], blueTmp[2][i][j + 1]); } } } else { if (bayerParams.pixelShiftDemosaicMethod == bayerParams.getPSDemosaicMethodString(procparams::RAWParams::BayerSensor::PSDemosaicMethod::LMMSE)) { lmmse_interpolate_omp(winw, winh, rawData, red, green, blue, bayerParams.lmmse_iterations); } else if (bayerParams.pixelShiftDemosaicMethod == bayerParams.getPSDemosaicMethodString(procparams::RAWParams::BayerSensor::PSDemosaicMethod::AMAZEVNG4)) { procparams::RAWParams rawParamsTmp = rawParamsIn; rawParamsTmp.bayersensor.method = procparams::RAWParams::BayerSensor::getMethodString(procparams::RAWParams::BayerSensor::Method::AMAZEVNG4); dual_demosaic_RT (true, rawParamsTmp, winw, winh, rawData, red, green, blue, bayerParams.dualDemosaicContrast, true); } else { amaze_demosaic_RT(winx, winy, winw, winh, rawData, red, green, blue, options.chunkSizeAMAZE, options.measure); } } } } constexpr float stddevFactorGreen = 25.f; constexpr float stddevFactorRed = 25.f; constexpr float stddevFactorBlue = 25.f; constexpr float prnu = 0.01f; constexpr float redBlueWeight = 0.7f + 1.f; float eperIso = bayerParams.pixelShiftEperIso; const bool checkNonGreenCross = bayerParams.pixelShiftNonGreenCross; const bool checkGreen = bayerParams.pixelShiftGreen; constexpr float greenWeight = 2.f; const bool blurMap = bayerParams.pixelShiftBlur; const float sigma = bayerParams.pixelShiftSigma; constexpr float noMotion = 0.99f; constexpr float threshold = 3.f + 9 * noMotion; const bool holeFill = bayerParams.pixelShiftHoleFill; const bool equalBrightness = bayerParams.pixelShiftEqualBright; const bool equalChannel = bayerParams.pixelShiftEqualBrightChannel; const bool smoothTransitions = blurMap && bayerParams.pixelShiftSmoothFactor > 0.; static const float nReadK3II[] = { 3.4f, // ISO 100 3.1f, // ISO 125 2.5f, // ISO 160 2.5f, // ISO 200 2.5f, // ISO 250 2.5f, // ISO 320 2.3f, // ISO 400 2.5f, // ISO 500 2.3f, // ISO 640 2.3f, // ISO 800 2.4f, // ISO 1000 2.3f, // ISO 1250 1.75f, // ISO 1600 1.75f, // ISO 2000 1.75f, // ISO 2500 1.75f, // ISO 3200 1.75f, // ISO 4000 1.75f, // ISO 5000 1.75f, // ISO 6400 1.75f, // ISO 8000 1.75f, // ISO 10000 1.5f, // ISO 12800 1.5f, // ISO 16000 1.5f, // ISO 20000 1.5f, // ISO 25600 1.5f, // ISO 32000 1.5f, // ISO 40000 1.5f // ISO 51200 }; static const float ePerIsoK3II = 0.35f; // currently we use the same values for K-1 and K-1 Mark II, though for the K-1 Mark II the values seem a bit aggressive static const float nReadK1[] = { 3.45f, // ISO 100 3.15f, // ISO 125 3.45f, // ISO 160 3.0f, // ISO 200 3.0f, // ISO 250 3.0f, // ISO 320 2.7f, // ISO 400 2.7f, // ISO 500 2.7f, // ISO 640 2.5f, // ISO 800 2.5f, // ISO 1000 2.5f, // ISO 1250 2.4f, // ISO 1600 2.4f, // ISO 2000 2.4f, // ISO 2500 2.4f, // ISO 3200 2.4f, // ISO 4000 2.4f, // ISO 5000 2.4f, // ISO 6400 2.4f, // ISO 8000 2.4f, // ISO 10000 2.4f, // ISO 12800 2.4f, // ISO 16000 2.4f, // ISO 20000 2.4f, // ISO 25600 2.4f, // ISO 32000 2.4f, // ISO 40000 2.4f, // ISO 51200 2.4f, // ISO 64000 2.4f, // ISO 80000 2.4f, // ISO 102400 2.4f, // ISO 128000 2.4f, // ISO 160000 2.4f, // ISO 204800 2.4f, // ISO 256000 // these are for K-1 Mark II to avoid crashes when using high-ISO files 2.4f, // ISO 320000 2.4f, // ISO 409600 2.4f, // ISO 512000 2.4f, // ISO 640000 2.4f // ISO 819200 }; static const float ePerIsoK1 = 0.75f; // currently nReadK70 is used for K-70 and KP static const float nReadK70[] = { 4.0f, // ISO 100 4.0f, // ISO 125 4.0f, // ISO 160 4.0f, // ISO 200 4.0f, // ISO 250 4.0f, // ISO 320 4.0f, // ISO 400 4.0f, // ISO 500 4.0f, // ISO 640 3.0f, // ISO 800 3.0f, // ISO 1000 3.0f, // ISO 1250 3.0f, // ISO 1600 3.0f, // ISO 2000 3.0f, // ISO 2500 3.0f, // ISO 3200 3.0f, // ISO 4000 3.0f, // ISO 5000 3.0f, // ISO 6400 3.0f, // ISO 8000 3.0f, // ISO 10000 3.0f, // ISO 12800 3.0f, // ISO 16000 3.0f, // ISO 20000 3.0f, // ISO 25600 3.0f, // ISO 32000 3.0f, // ISO 40000 3.0f, // ISO 51200 3.0f, // ISO 64000 3.0f, // ISO 80000 3.0f, // ISO 102400 3.0f, // ISO 128000 3.0f, // ISO 160000 3.0f, // ISO 204800 3.0f, // ISO 256000 3.0f, // ISO 320000 3.0f, // ISO 409600 3.0f, // ISO 512000 3.0f, // ISO 640000 3.0f // ISO 819200 }; static const float ePerIsoK70 = 0.5f; // preliminary ILCE-7RM3 data, good fidelity except from A) small inaccuracy at places // due to integer scaling quantization, B) much different noise behaviour of PDAF pixels static const float nReadILCE7RM3[] = { 4.2f, // ISO 100 3.9f, // ISO 125 3.6f, // ISO 160 3.55f, // ISO 200 3.5f, // ISO 250 3.45f, // ISO 320 3.35f, // ISO 400 3.3f, // ISO 500 1.3f, // ISO 640 1.2f, // ISO 800 1.2f, // ISO 1000 1.2f, // ISO 1250 1.15f, // ISO 1600 1.2f, // ISO 2000 1.15f, // ISO 2500 1.15f, // ISO 3200 1.1f, // ISO 4000 1.1f, // ISO 5000 1.05f, // ISO 6400 1.05f, // ISO 8000 1.05f, // ISO 10000 1.0f, // ISO 12800 1.0f, // ISO 16000 1.0f, // ISO 20000 1.0f, // ISO 25600 1.0f, // ISO 32000 1.0f, // ISO 40000 1.0f, // ISO 51200 1.1f, // ISO 64000 1.1f, // ISO 80000 1.1f, // ISO 102400 }; static const float ePerIsoILCE7RM3 = 0.8f; if(plistener) { plistener->setProgressStr(Glib::ustring::compose(M("TP_RAW_DMETHOD_PROGRESSBAR"), M("TP_RAW_PIXELSHIFT"))); plistener->setProgress(0.0); } if(motionDetection && blurMap && smoothFactor == 0.f && !showMotion) { if(plistener) { plistener->setProgress(1.0); } return; } float nRead; float eperIsoModel; int nReadIndex = static_cast(round(log2(idata->getISOSpeed() / 100.f) * 3.f)); if(model.find("K-3") != string::npos) { nRead = nReadK3II[nReadIndex]; eperIsoModel = ePerIsoK3II; } else if(model.find("K-1") != string::npos) { // this also matches K-1 Mark II nRead = nReadK1[nReadIndex]; eperIsoModel = ePerIsoK1; } else if(model.find("ILCE-7RM3") != string::npos) { nRead = nReadILCE7RM3[nReadIndex]; eperIsoModel = ePerIsoILCE7RM3; } else { // as long as we don't have values for Pentax KP, we use the values from K-70 nRead = nReadK70[nReadIndex]; eperIsoModel = ePerIsoK70; } eperIsoModel *= pow(2.f, eperIso - 1.f); eperIso = eperIsoModel * (100.f / (rawWpCorrection * idata->getISOSpeed())); const float eperIsoRed = (eperIso / scale_mul[0]) * (65535.f / (c_white[0] - c_black[0])); const float eperIsoGreen = (eperIso / scale_mul[1]) * (65535.f / (c_white[1] - c_black[1])); const float eperIsoBlue = (eperIso / scale_mul[2]) * (65535.f / (c_white[2] - c_black[2])); const float clippedRed = 65535.f / scale_mul[0]; const float clippedBlue = 65535.f / scale_mul[2]; nRead *= nRead; // calculate channel median brightness for each frame float greenBrightness[4] = {1.f, 1.f, 1.f, 1.f}; float redBrightness[4] = {1.f, 1.f, 1.f, 1.f}; float blueBrightness[4] = {1.f, 1.f, 1.f, 1.f}; if(equalBrightness) { if(rawDirty) { LUTu *histogreen[4]; LUTu *histored[4]; LUTu *histoblue[4]; for(int i = 0; i < 4; ++i) { histogreen[i] = new LUTu(65536, LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true); histored[i] = new LUTu(65536, LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true); histoblue[i] = new LUTu(65536, LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true); } #ifdef _OPENMP #pragma omp parallel #endif { LUTu *histogreenThr[4]; LUTu *historedThr[4]; LUTu *histoblueThr[4]; for(int i = 0; i < 4; ++i) { histogreenThr[i] = new LUTu(65536, LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true); historedThr[i] = new LUTu(65536, LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true); histoblueThr[i] = new LUTu(65536, LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true); } #ifdef _OPENMP #pragma omp for schedule(dynamic,16) nowait #endif for(int i = winy + 1; i < winh - 1; ++i) { int j = winx + 1; int c = FC(i, j); bool bluerow = (c + FC(i, j + 1)) == 3; for(int j = winx + 1, offset = FC(i, j) & 1; j < winw - 1; ++j, offset ^= 1) { (*histogreenThr[1 - offset])[(*rawDataFrames[1 - offset])[i - offset + 1][j]]++; (*histogreenThr[3 - offset])[(*rawDataFrames[3 - offset])[i + offset][j + 1]]++; if(bluerow) { (*historedThr[2 - offset])[(*rawDataFrames[2 - offset])[i + 1][j - offset + 1]]++; (*histoblueThr[(offset << 1) + offset])[(*rawDataFrames[(offset << 1) + offset])[i][j + offset]]++; } else { (*historedThr[(offset << 1) + offset])[(*rawDataFrames[(offset << 1) + offset])[i][j + offset]]++; (*histoblueThr[2 - offset])[(*rawDataFrames[2 - offset])[i + 1][j - offset + 1]]++; } } } #ifdef _OPENMP #pragma omp critical #endif { for(int i = 0; i < 4; ++i) { (*histogreen[i]) += (*histogreenThr[i]); delete histogreenThr[i]; (*histored[i]) += (*historedThr[i]); delete historedThr[i]; (*histoblue[i]) += (*histoblueThr[i]); delete histoblueThr[i]; } } } calcFrameBrightnessFactor(frame, (winh - 2) * (winw - 2) / 4, histored, redBrightness); calcFrameBrightnessFactor(frame, (winh - 2) * (winw - 2) / 4, histoblue, blueBrightness); calcFrameBrightnessFactor(frame, (winh - 2) * (winw - 2) / 2, histogreen, greenBrightness); for(int i = 0; i < 4; ++i) { psRedBrightness[i] = redBrightness[i]; psGreenBrightness[i] = greenBrightness[i]; psBlueBrightness[i] = blueBrightness[i]; } rawDirty = false; for(int i = 0; i < 4; ++i) { delete histored[i]; delete histoblue[i]; delete histogreen[i]; } if(plistener) { plistener->setProgress(0.15); } } else { for(int i = 0; i < 4; ++i) { redBrightness[i] = psRedBrightness[i]; greenBrightness[i] = psGreenBrightness[i]; blueBrightness[i] = psBlueBrightness[i]; } } if(!equalChannel) { for(int i = 0; i < 4; ++i) { redBrightness[i] = blueBrightness[i] = greenBrightness[i]; } } } if(motionDetection) { // fill channels psRed and psBlue array2D psRed(winw + 32, winh); // increase width to avoid cache conflicts array2D psBlue(winw + 32, winh); #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for(int i = winy + 1; i < winh - 1; ++i) { float *nonGreenDest0 = psRed[i]; float *nonGreenDest1 = psBlue[i]; float ngbright[2][4] = {{redBrightness[0], redBrightness[1], redBrightness[2], redBrightness[3]}, {blueBrightness[0], blueBrightness[1], blueBrightness[2], blueBrightness[3]} }; int ng = 0; int j = winx + 1; int c = FC(i, j); if((c + FC(i, j + 1)) == 3) { // row with blue pixels => swap destination pointers for non green pixels std::swap(nonGreenDest0, nonGreenDest1); ng ^= 1; } // offset to keep the code short. It changes its value between 0 and 1 for each iteration of the loop unsigned int offset = c & 1; for(; j < winw - 1; ++j) { // store the non green values from the 4 frames into 2 temporary planes nonGreenDest0[j] = (*rawDataFrames[(offset << 1) + offset])[i][j + offset] * ngbright[ng][(offset << 1) + offset]; nonGreenDest1[j] = (*rawDataFrames[2 - offset])[i + 1][j - offset + 1] * ngbright[ng ^ 1][2 - offset]; offset ^= 1; // 0 => 1 or 1 => 0 } } if(plistener) { plistener->setProgress(0.3); } // now that the temporary planes are filled for easy access we do the motion detection array2D psMask(winw, winh); int offsX = 0, offsY = 0; if(!bayerParams.pixelShiftMedian) { // We have to adjust the offsets for the selected subframe we use for areas with motion switch(frame) { case 0: offsX = offsY = 0; break; case 1: offsX = 0; offsY = 1; break; case 2: offsX = offsY = 1; break; case 3: offsX = 1; offsY = 0; } } #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for(int i = winy + border - offsY; i < winh - (border + offsY); ++i) { // offset to keep the code short. It changes its value between 0 and 1 for each iteration of the loop unsigned int offset = FC(i, winx + border - offsX) & 1; for(int j = winx + border - offsX; j < winw - (border + offsX); ++j, offset ^= 1) { psMask[i][j] = noMotion; if(checkGreen) { if(greenDiff((*rawDataFrames[1 - offset])[i - offset + 1][j] * greenBrightness[1 - offset], (*rawDataFrames[3 - offset])[i + offset][j + 1] * greenBrightness[3 - offset], stddevFactorGreen, eperIsoGreen, nRead, prnu) > 0.f) { psMask[i][j] = greenWeight; // do not set the motion pixel values. They have already been set by demosaicer continue; } } if(checkNonGreenCross) { // check red cross float redTop = psRed[i - 1][j]; float redLeft = psRed[i][j - 1]; float redCentre = psRed[i][j]; float redRight = psRed[i][j + 1]; float redBottom = psRed[i + 1][j]; float redDiff = nonGreenDiffCross(redRight, redLeft, redTop, redBottom, redCentre, clippedRed, stddevFactorRed, eperIsoRed, nRead, prnu); if(redDiff > 0.f) { psMask[i][j] = redBlueWeight; continue; } // check blue cross float blueTop = psBlue[i - 1][j]; float blueLeft = psBlue[i][j - 1]; float blueCentre = psBlue[i][j]; float blueRight = psBlue[i][j + 1]; float blueBottom = psBlue[i + 1][j]; float blueDiff = nonGreenDiffCross(blueRight, blueLeft, blueTop, blueBottom, blueCentre, clippedBlue, stddevFactorBlue, eperIsoBlue, nRead, prnu); if(blueDiff > 0.f) { psMask[i][j] = redBlueWeight; continue; } } } } if(plistener) { plistener->setProgress(0.45); } if(blurMap) { #ifdef _OPENMP #pragma omp parallel #endif { gaussianBlur(psMask, psMask, winw, winh, sigma); } if(plistener) { plistener->setProgress(0.6); } } array2D mask(winw, winh, ARRAY2D_CLEAR_DATA); #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for(int i = winy + border - offsY; i < winh - (border + offsY); ++i) { int j = winx + border - offsX; float v3sum[3] = {0.f}; for(int v = -1; v <= 1; v++) { for(int h = -1; h < 1; h++) { v3sum[1 + h] += psMask[i + v][j + h]; } } float blocksum = v3sum[0] + v3sum[1]; for(int voffset = 2; j < winw - (border + offsX); ++j, ++voffset) { float colSum = psMask[i - 1][j + 1] + psMask[i][j + 1] + psMask[i + 1][j + 1]; voffset = voffset == 3 ? 0 : voffset; // faster than voffset %= 3; blocksum -= v3sum[voffset]; blocksum += colSum; v3sum[voffset] = colSum; if(blocksum >= threshold) { mask[i][j] = 255; } } } if(plistener) { plistener->setProgress(0.75); } if(holeFill) { array2D maskInv(winw, winh); invertMask(winx + border - offsX, winw - (border + offsX), winy + border - offsY, winh - (border + offsY), mask, maskInv); floodFill4(winx + border - offsX, winw - (border + offsX), winy + border - offsY, winh - (border + offsY), maskInv); xorMasks(winx + border - offsX, winw - (border + offsX), winy + border - offsY, winh - (border + offsY), maskInv, mask); } if(plistener) { plistener->setProgress(0.9); } #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for(int i = winy + border - offsY; i < winh - (border + offsY); ++i) { #ifdef __SSE2__ // pow() is expensive => pre calculate blend factor using SSE if(smoothTransitions) { // vfloat onev = F2V(1.f); vfloat smoothv = F2V(smoothFactor); int j = winx + border - offsX; for(; j < winw - (border + offsX) - 3; j += 4) { vfloat blendv = vmaxf(LVFU(psMask[i][j]), onev) - onev; blendv = pow_F(blendv, smoothv); blendv = vself(vmaskf_eq(smoothv, ZEROV), onev, blendv); STVFU(psMask[i][j], blendv); } for(; j < winw - (border + offsX); ++j) { psMask[i][j] = smoothFactor == 0.f ? 1.f : pow_F(std::max(psMask[i][j] - 1.f, 0.f), smoothFactor); } } #endif float *greenDest = green[i + offsY]; float *redDest = red[i + offsY]; float *blueDest = blue[i + offsY]; // offset to keep the code short. It changes its value between 0 and 1 for each iteration of the loop unsigned int offset = FC(i, winx + border - offsX) & 1; for(int j = winx + border - offsX; j < winw - (border + offsX); ++j, offset ^= 1) { if(showOnlyMask) { if(smoothTransitions) { // we want only motion mask => paint areas according to their motion (dark = no motion, bright = motion) #ifdef __SSE2__ // use pre calculated blend factor const float blend = psMask[i][j]; #else const float blend = smoothFactor == 0.f ? 1.f : pow_F(std::max(psMask[i][j] - 1.f, 0.f), smoothFactor); #endif redDest[j + offsX] = greenDest[j + offsX] = blueDest[j + offsX] = blend * 32768.f; } else { redDest[j + offsX] = greenDest[j + offsX] = blueDest[j + offsX] = mask[i][j] == 255 ? 65535.f : 0.f; } } else if(mask[i][j] == 255) { paintMotionMask(j + offsX, showMotion, greenDest, redDest, blueDest); } else { if(smoothTransitions) { #ifdef __SSE2__ // use pre calculated blend factor const float blend = psMask[i][j]; #else const float blend = smoothFactor == 0.f ? 1.f : pow_F(std::max(psMask[i][j] - 1.f, 0.f), smoothFactor); #endif redDest[j + offsX] = intp(blend, showMotion ? 0.f : redDest[j + offsX], psRed[i][j] ); greenDest[j + offsX] = intp(blend, showMotion ? 13500.f : greenDest[j + offsX], ((*rawDataFrames[1 - offset])[i - offset + 1][j] * greenBrightness[1 - offset] + (*rawDataFrames[3 - offset])[i + offset][j + 1] * greenBrightness[3 - offset]) * 0.5f); blueDest[j + offsX] = intp(blend, showMotion ? 0.f : blueDest[j + offsX], psBlue[i][j]); } else { redDest[j + offsX] = psRed[i][j]; greenDest[j + offsX] = ((*rawDataFrames[1 - offset])[i - offset + 1][j] * greenBrightness[1 - offset] + (*rawDataFrames[3 - offset])[i + offset][j + 1] * greenBrightness[3 - offset]) * 0.5f; blueDest[j + offsX] = psBlue[i][j]; } } } } } else { // motion detection off => combine the 4 raw frames float ngbright[2][4] = {{redBrightness[0], redBrightness[1], redBrightness[2], redBrightness[3]}, {blueBrightness[0], blueBrightness[1], blueBrightness[2], blueBrightness[3]} }; #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) #endif for(int i = winy + 1; i < winh - 1; ++i) { float *nonGreenDest0 = red[i]; float *nonGreenDest1 = blue[i]; int ng = 0; int j = winx + 1; int c = FC(i, j); if((c + FC(i, j + 1)) == 3) { // row with blue pixels => swap destination pointers for non green pixels std::swap(nonGreenDest0, nonGreenDest1); ng ^= 1; } // offset to keep the code short. It changes its value between 0 and 1 for each iteration of the loop unsigned int offset = c & 1; for(; j < winw - 1; ++j) { // set red, green and blue values green[i][j] = ((*rawDataFrames[1 - offset])[i - offset + 1][j] * greenBrightness[1 - offset] + (*rawDataFrames[3 - offset])[i + offset][j + 1] * greenBrightness[3 - offset]) * 0.5f; nonGreenDest0[j] = (*rawDataFrames[(offset << 1) + offset])[i][j + offset] * ngbright[ng][(offset << 1) + offset]; nonGreenDest1[j] = (*rawDataFrames[2 - offset])[i + 1][j - offset + 1] * ngbright[ng ^ 1][2 - offset]; offset ^= 1; // 0 => 1 or 1 => 0 } } } if(plistener) { plistener->setProgress(1.0); } }