decoded_low is a logical array representing the decoded bits of a uint8 image. As a consequence, numel(decoded_low) and numel(lenna_gray) differ by a factor of 8.
You're presuming that the input is integer-valued, but not checking. You're also presuming that de2bi() will produce a predictable word width. The output width will depend on the range of input values. You actually have to specify it if you want it to be consistent.
You can either force the output to be a consistent width as determined by the integer class:
% Load the lenna image
lenna = imread('peppers.png');
% Convert image to grayscale
lenna_gray = im2gray(lenna);
% Convert pixel values to bits
% either figure out the width programmatically and enforce integer inputs
% or recast to a known integer class
lenna_gray = im2uint8(lenna_gray); % now it's known
bpp = 8;
lenna_bits = reshape(de2bi(lenna_gray,bpp), [], 1);
% modulation stuff
% ...
% i'm going to ignore all that
decoded_low = lenna_bits; % just devectorize the input
% Reshape decoded bits to original image size
insize = size(lenna_gray); % just get this once
decoded_image_low = reshape(decoded_low, [], bpp); % this relies on knowing the width
decoded_image_low = bi2de(decoded_image_low); % it needs to be converted back to numeric
decoded_image_low = reshape(decoded_image_low, insize);
% Plot original and received image at low SNR
figure;
subplot(1,2,1); imshow(lenna_gray); title('Original Image');
subplot(1,2,2); imshow(decoded_image_low); title('Received Image (0 dB SNR)');
Or you can let it be whatever width is required to hold the range of values in the particular array.
% Load the lenna image
lenna = imread('peppers.png');
% Convert image to grayscale
lenna_gray = im2gray(lenna);
% or you can just let lenna_bits be whatever width is determined
% by the range of values therein
lenna_gray = im2uint8(lenna_gray); % now it's known
lenna_bits = reshape(de2bi(lenna_gray), [], 1);
% modulation stuff
% ...
% i'm going to ignore all that
decoded_low = lenna_bits;
% Reshape decoded bits to original image size
insize = size(lenna_gray); % just get this once
decoded_image_low = reshape(decoded_low, prod(insize), []); % don't need to know the width
decoded_image_low = bi2de(decoded_image_low);
decoded_image_low = reshape(decoded_image_low, insize);
% Plot original and received image at low SNR
figure;
subplot(1,2,1); imshow(lenna_gray); title('Original Image');
subplot(1,2,2); imshow(decoded_image_low); title('Received Image (0 dB SNR)');
Obviously, I cut out the modulation stuff, but this might help straighten out the vectorization/devectorization task.