简体   繁体   中英

Data in `std::vector` different when using `std::unique_ptr`

I have written a custom class that stores images and eventually computes a calibration based on those images, but I am encountering an issue in the way that the images are stored. I have two overloaded functions that do this, with one reading the images from file using cv::imread , and the other using an intermediate Snapshot data structure for holding the data. The function using cv::imread works fine, but the one using the custom data structure does not. I am trying to store three images right now, and the issue is that as I push the images into a vector, the data for the second images is copied into the first one.

This is the working function:

bool CalibClass::AddImage(const std::string& snapshotPath) {
    cv::Mat img = cv::imread(snapshotPath);

    // _snapshots is a private member declared as a std::vector<cv::Mat>
    _snapshots.push_back(img);

    return true;
}

This is the function that is not working:

bool CalibClass::AddImage(const ImageSet& snapshot) {

    RGBImage *rgb_image_ptr = snapshot.GetRGBImage();

    std::vector<unsigned char> img_data(rgb_image_ptr->GetData());
    cv::Mat img(rgb_image_ptr->GetHeight(), rgb_image_ptr->GetWidth(), CV_8UC3, img_data.data());

    _snapshots.push_back(img);

    return true;
}

The ImageSet class stores images as an std::unique_ptr<RGBImage> . The RGBImage class stores the image data as a std::vector<unsigned char> .

This is how the images are loaded into the class from the main :

cv::Mat img1 = cv::imread("img1.png");
cv::Mat img2 = cv::imread("img2.png");      
cv::Mat img3 = cv::imread("img3.png");

int length = img1.total() * img1.elemSize();

std::vector<unsigned char> data1;
std::vector<unsigned char> data2;
std::vector<unsigned char> data3;
for (int i = 0; i < length; i++) {
    data1.push_back(img1.data[i]);
}

for (int i = 0; i < length; i++) {
    data2.push_back(img2.data[i]);
}

for (int i = 0; i < length; i++) {
    data3.push_back(img3.data[i]);
}


CalibClass calib_test;

std::unique_ptr<RGBImage> rgb_image_ptr1(new RGBImage(img1.rows, img1.cols, data1));
ImageSet new_snap1(rgb_image_ptr1, nullptr, 0);
calib_test.AddImage(new_snap1);


std::unique_ptr<RGBImage> rgb_image_ptr2(new RGBImage(img2.rows, img2.cols, data2));
ImageSet new_snap2(rgb_image_ptr2, nullptr, 0);
calib_test.AddImage(new_snap2);

std::unique_ptr<RGBImage> rgb_image_ptr3(new RGBImage(img3.rows, img3.cols, data3));
ImageSet new_snap3(rgb_image_ptr3, nullptr, 0);
calib_test.AddImage(new_snap3);

When I put a break point inside the function and check the content of the _snapshots , the first element is the second image, and the second and third elements are the third image. When I set a break point after all the AddImage() calls, the content of _snapshots has the second image as the first element, the third image as the second element, and the third element has a cv::Mat with invalid data.

What is the reason why the two methods are storing the images differently? What would be the way to fix this issue?

Those symptoms sound a lot like there is a shallow copy going on, which would mean undefined behavior in the second approach since the cv::Mat in the vector outlives img_data . Let me see if I can find documentation for the constructor you used.

Found it here . Yes, it does a shallow copy (emphasis added):

Matrix constructors that take data and step parameters do not allocate matrix data. Instead, they just initialize the matrix header that points to the specified data, which means that no data is copied .

So when the second approach pushes an image onto _snapshots , that image's data lives in the local variable img_data . Then the function ends, making that data invalid. Thus you get undefined behavior when you look at the data.

To solve this, you would need to make sure the data gets copied. You would also want to make sure the data gets freed at some point to avoid a memory leak. One approach is to define a class consisting of a cv::Mat and something to store the data, perhaps a std::vector<unsigned char> . (Use the latter member instead of the local variable img_data .) A starting point might be the following:

class MatWrapper {
    public:
        explicit MatWrapper(const RGBImage & rgb_image) :
            data(rgb_image.GetData()),
            image(rgb_image.GetHeight(), rgb_image.GetWidth(), CV_8UC3, data.data())
        {}

    private:
        std::vector<unsigned char> data; // Declaration order matters!
        cv::Mat image;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM