-2

I have created a simple Python class that takes two values as arguments when instantiating it (contents and resolution). These arguments are then assigned to class members in the class' __init__ function.

For some reason, one of the class members (contents) appears to be a reference/pointer while the other is not (resolution). If I edit contents on one class, it updates the other even though I've instantiated two completely separate instances of the class. Here's the stripped down example:

TestBaseClasses.py

import cv2

class Frame():
    def __init__(self, contents, resolution):
        self.contents = contents
        self.resolution = [resolution[0], resolution[1]]

    def resize(self, fx, fy):
        self.contents = cv2.resize(self.contents, (0, 0), fx=fx, fy=fy)

test.py

import cv2
from copy import deepcopy
from TestBaseClasses import Frame

frame = cv2.imread("test_img.png")
h, w, _ = frame.shape

ProcessFrame = Frame(frame, [h, w])
OriginalFrame = Frame(frame, [h, w])

print(type(frame))
print(ProcessFrame is OriginalFrame)
print(ProcessFrame.contents is OriginalFrame.contents)
print(ProcessFrame.resolution is OriginalFrame.resolution)
print(id(ProcessFrame.contents))
print(id(OriginalFrame.contents))

print("########################")

ProcessFrame = Frame(deepcopy(frame), [h, w])
OriginalFrame = Frame(deepcopy(frame), [h, w])

print(type(frame))
print(ProcessFrame is OriginalFrame)
print(ProcessFrame.contents is OriginalFrame.contents)
print(ProcessFrame.resolution is OriginalFrame.resolution)
print(id(ProcessFrame.contents))
print(id(OriginalFrame.contents))

Output from test.py

<class 'numpy.ndarray'>
False
True
False
4405193824
4405193824
########################
<class 'numpy.ndarray'>
False
False
False
4409151200
4491382256

As you can see, I have to make a deepcopy of the frame variable in order to prevent it from being linked to the same reference in memory. You can also see that the frame variables' type is a numpy array (not some sort of reference/pointer).

When I use example one (without deepcopy) and edit the contents member in ProcessFrame, it edits the contents member in OriginalFrame. This does not happen when I do the same for resolution.

What in the world is going on here? I'd like to not have to import the copy module and use deepcopy if I can avoid it.

It should be noted that I am using Python 3.6.


Update

I am beginning to think this is related to the cv2.imread() function. If I create a new class member and assign contents to it, it has the same value using id() (i.e. it's pointing to the same place in memory).

Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
MillerMedia
  • 3,651
  • 17
  • 71
  • 150
  • 3
    `cv2` reads images as NumPy `ndarray`s, so that's not surprising. Also, NumPy does not make copies of arrays unless specifically told to, because they can be so large that making copies willy-nilly could rapidly use up memory. There's a better (i.e., more technical) explanation lying around, I'll see if I can find it. – MattDMo Jan 09 '22 at 04:43
  • Thanks @MattDMo. I think I just found the answer, will answer my own question on here as I would guess others could make this mistake and haven't seen another answer to it anywhere. – MillerMedia Jan 09 '22 at 04:44

1 Answers1

-1

In the OpenCV docs here:
https://docs.opencv.org/3.4/d5/d98/tutorial_mat_operations.html

In the section 'Memory management and reference counting', it mentions that the data read in using OpenCV imread function is the equivalent of Mat in C++. It goes on:

Mat is a structure that keeps matrix/image characteristics (rows and columns number, data type etc) and a pointer to data. So nothing prevents us from having several instances of Mat corresponding to the same data. A Mat keeps a reference count that tells if data has to be deallocated when a particular instance of Mat is destroyed.

Thus, although Python returns <class 'numpy.ndarray'> as the type, it is actually a pointer/reference (don't know the difference in Python, someone please expand on that) to that array in memory. In it's example they go on to show how to make copies of the image data:

img = cv.imread('image.jpg')
_img1 = np.copy(img)

In my case, I just updated the code in the Frame class where self.contents is initialized to:

self.contents = np.copy(contents)

And that fixed the problem.

MillerMedia
  • 3,651
  • 17
  • 71
  • 150
  • this has nothing to do with OpenCV and everything to do with how `numpy` arrays work... and actually how all of python works. Python has no concept of "pointers". every variable is a reference to an object. some objects are immutable (tuples, strings, numbers), some are mutable (lists, numpy arrays, instances of most classes). assigning an object to two variables merely causes both variables to refer to the same (identical) object. – Christoph Rackwitz Jan 09 '22 at 12:49