You could return the “compressed representation” additionally to the last output from your model after training:
    def forward(self, x, return_comp=False):
        ## encode ##
        # add hidden layers with relu activation function
        # and maxpooling after
        x = self.relu(self.conv1(x))
        x = self.pool(x)
        # add second hidden layer
        x = self.relu(self.conv2(x))
        x_comp = self.pool(x)  # compressed representation
        
        ## decode ##
        # add transpose conv layers, with relu activation function
        x = self.relu(self.t_conv1(x_comp))
        # output layer (with sigmoid for scaling from 0 to 1)
        x = self.sigmoid(self.t_conv2(x))
        
        if return_comp:
            return x, x_comp
        else:
            return x

Then just set return_comp=True and store all features using torch.save (with the corresponding classes).
Afterwards, you can setup a new CNN and load this data to train it.