In the first case, your convolution should keep the spatial shape of your input.
To achieve this, your padding should be ((kernel_size) - 1) / 2 for odd sized kernels (and default stride, dilation etc.).
This code would give you the same shape:
n = 5
c, h, w = 3, 12, 12
x1 = torch.randn(n, c, h, w)

conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3, padding=1)
output1 = conv1(x1)
print(output1.shape)
> torch.Size([5, 3, 12, 12])

In the second example it seems your input images are smaller so that you could use nn.ConvTranspose2d to increase the spatial size:
x2 = torch.randn(n, c, h//2, w//2)

conv2 = nn.ConvTranspose2d(in_channels=3, out_channels=3, kernel_size=2, stride=2)
output2 = conv2(x2)
print(output2.shape)
> torch.Size([5, 3, 12, 12])