3

I'm trying to wrap a C++ class which uses OpenCv so that I can use it in C#.

I have the C++ function:

void ImageBrightener::BrightenImage(const cv::Mat& sourceImage, cv::Mat& targetImage, int maxTarget)
{
    double scaleFactor;
    double shiftFactor = 0;
    double minVal = DBL_MAX, minValTemp;
    double maxVal = -DBL_MAX, maxValTemp;
    auto numPixels = 0;
    const auto RANGE_TOP_EXTEND = 10;
    const auto RANGE_BOTTOM_EXTEND = 7;

    assert(sourceImage.type() == CV_8UC1);
    assert(sourceImage.channels() == 1);

    cv::minMaxIdx(sourceImage, &minValTemp, &maxValTemp);
    if (minValTemp < minVal)
    minVal = minValTemp;
    if (maxValTemp>maxVal)
        maxVal = maxValTemp;

    numPixels += sourceImage.cols * sourceImage.rows;

    if (maxVal == minVal)
    {
        sourceImage.convertTo(targetImage, CV_8UC1, 1, shiftFactor);
        return;
    }

    // Account for prev/curr ROI differences - add a bit to the range
    maxVal += RANGE_TOP_EXTEND;
    minVal -= RANGE_BOTTOM_EXTEND;
    minVal = std::max(minVal, 0.);

    if ((maxVal - minVal) < maxTarget)
    {
        scaleFactor = maxTarget / (maxVal - minVal);
        shiftFactor = -1 * scaleFactor * minVal;

        sourceImage.convertTo(targetImage, CV_8UC1, scaleFactor, shiftFactor);
        return;
    }

    auto fltMinVal = static_cast<float>(minVal) - 1;
    auto fltMaxVal = static_cast<float>(maxVal) + 1;

    // Check histogram
    const unsigned int *currval;

    #define BINS   (100)
    #define CUTOFF (0.00003)
    #define RESCUTOFF (0.2)

    int hist[BINS] = { 0 };
    int bin;

    numPixels += sourceImage.cols * sourceImage.rows;
    for (auto rowIndex = 0; rowIndex < sourceImage.rows; rowIndex++)
    {
        currval = sourceImage.ptr<unsigned>(rowIndex, 0);
        for (auto colIndex = 0; colIndex < sourceImage.cols; colIndex++)
        {
            bin = static_cast<int>((BINS - 1) * ((*currval - fltMinVal) / (fltMaxVal - fltMinVal)));
            assert(bin >= 0 && bin < BINS);
            hist[bin]++;
            ++currval;
        }
    }

    double ratio;
    auto sum = 0;
    int i;
    for (i = BINS - 1; i >= 0; i--)
    {
        sum += hist[i];
        ratio = static_cast<double>(sum) / static_cast<double>(numPixels);
        if (ratio > CUTOFF)
            break;
    }
    if (static_cast<double>(BINS - i) / static_cast<double>(BINS) > RESCUTOFF)
        fltMaxVal = fltMinVal + ((i + 2)*(fltMaxVal - fltMinVal)) / BINS;

    // Account for prev/curr ROI differences - add a bit to the range
    fltMaxVal += RANGE_TOP_EXTEND;
    fltMinVal -= RANGE_BOTTOM_EXTEND;
    fltMinVal = std::max(fltMinVal, 0.f);

    scaleFactor = maxTarget / (fltMaxVal - fltMinVal);
    shiftFactor = -1 * scaleFactor * fltMinVal;

    sourceImage.convertTo(targetImage, CV_8UC1, scaleFactor, shiftFactor);
}

When I test this code using the following C++ code:

int main()
{
    auto* m_imageBrightener = new ImageBrightener();
    auto inputImage = cv::imread("E:\\ttt.png", CV_LOAD_IMAGE_UNCHANGED);

    cv::Mat outputImage;

    m_imageBrightener->BrightenImage(inputImage, outputImage, 2000);
    cv::imwrite("E:\\new_ttt.png", outputImage);
}

Everything works, the code does what it should, which is to get a dark 8bit image, and brighten it (I tried replacing 200 with 500 - it works fine). The new_ttt.png image is brightened as expected.

On the other hand, I have the following /Clr code, which wraps the C++ code and creates a DLL from it:

array<System::Byte>^ ImageProcessing::ImageBrightenerWrapper::BrightenImage(array<System::Byte>^ sourceImage, int imageWidth, int imageHeight, int maxTarget)
{
    array<System::Byte>^ targetImage = (array<System::Byte>^)sourceImage->Clone();

    pin_ptr<System::Byte> sourcePointer = &sourceImage[0];
    pin_ptr<System::Byte> targetPointer = &targetImage[0];

    cv::Mat sourceMat(imageHeight, imageWidth, CV_8UC1, (unsigned short*)sourcePointer);
    cv::Mat targetMat(imageHeight, imageWidth, CV_8UC1, (unsigned short*)targetPointer);

    targetMat.setTo(0);
    m_imageBrightener->BrightenImage(sourceMat, targetMat, maxTarget);

    uchar* tempPointer;
    for (auto rowIndex = 0; rowIndex < imageHeight; ++rowIndex)
    {
        tempPointer = targetMat.ptr<uchar>(rowIndex);
        for (auto colIndex = 0; colIndex < imageWidth; ++colIndex)
            targetImage[rowIndex + colIndex] = tempPointer[colIndex];
    }

    return targetImage;
}

With it, I also have a WPF application which has a slider which controls the maxTarget parameter.

This is what I'm facing:

1) On the one hand, any value between 0 and 960 for maxTarget brightens the row index which matches maxTarget / 2 - Meaning, that when I slide the slider to the right, for greater values, I get part of the image bright and the rest is as the original. (Example: if maxTarget is 300, then all of the rows between row #0 and row #150 will be brighter and the rest will be like the original).

2) On the other hand, if I cross the value if 960 for maxTarget then the application crashes with the following error (even though the code is surrounded with try/catch):

"An exception of type: 'System.AccessViolationException' occurred in ImageBrightenerWrapper.dll but was not handled in user code. Additional information: Attempted to read or write protected memory. This is pften an indication that other memory is corrupt."

What am I doing wrong here?

1 Answer 1

1

So I found two main issues to solve the problem:

1) I mistakenly mixed the parameters order from the C# code.

2) I had to replace the following line:

cv::Mat sourceMat(imageHeight, imageWidth, CV_8UC1, (unsigned short*)sourcePointer);

With this one:

cv::Mat sourceMat(imageHeight, imageWidth, CV_16UC1, (unsigned short*)sourcePointer);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.