I’m having problems implementing steganographic methods for video. I am trying to implement a simple LSB coder and decoder in YUV components using Hamming code (7,4) for error correction. My code can only decode the secret message from video frames stored on disk (but not from those extracted from reconstructed stego-video). I think that there will be a problem in the conversion to video and back to frames because it changes the pixels of the images only a little bit but it is fatal for LSB because Hamming code corrects only 1-bit errors. So the message is not readable after extracting the stego-frames containing the message from the stego-video. But I don’t understand why, because I avoid any compression.
How to process a video and frames to solve my problem with unreadable message after reconstruction of stego-video?
I am writing this code in Python using OpenCV. I also tried to process video with cv2.VideoWriter but it was not any better.
def video_to_rgb_frames(video_path):
"""Extracts frames from a video file and saves them as individual image files into "/frames" folder. Save as .png files."""
if not os.path.exists("./frames"):
os.makedirs("frames")
temporal_folder="./frames"
print("[INFO] frames directory is created")
capture = cv2.VideoCapture(video_path)
if not capture.isOpened():
print("Error: Video file cannot be opened!")
return
video_properties = {
"format": capture.get(cv2.CAP_PROP_FORMAT),
"codec": capture.get(cv2.CAP_PROP_FOURCC),
"container": capture.get(cv2.CAP_PROP_POS_AVI_RATIO),
"fps": capture.get(cv2.CAP_PROP_FPS),
"frames": capture.get(cv2.CAP_PROP_FRAME_COUNT),
"width": int(capture.get(cv2.CAP_PROP_FRAME_WIDTH)),
"height": int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT)),
}
frame_count = 0
while True:
ret, frame = capture.read()
if frame is None:
break
frame_count += 1
cv2.imwrite(os.path.join(temporal_folder, f"frame_{frame_count}.png"), frame)
capture.release()
print("[INFO] extraction finished")
return video_properties
def reconstruct_video_from_rgb_frames(file_path, properties, ffmpeg_path = r".srcutilsffmpeg.exe"):
"""Reconstruct video from RGB frames with ffmpeg."""
fps = properties["fps"]
codec = decode_fourcc(properties["codec"])
#file_extension = file_path.rsplit(".", 1)[1]
file_extension = "avi"
bitrate = get_vid_stream_bitrate(file_path)
if has_audio_track(file_path):
#extract audio stream from video
extract_audio_track(file_path)
#recreate video from frames (without audio)
call([ffmpeg_path, "-r", str(fps), "-i", "frames/frame_%d.png" , "-vcodec", str(codec), "-b", str(bitrate),"-crf", "0","-pix_fmt", "yuv420p", f"tmp/video.{file_extension}", "-y"])
#add audio to a recreated video
call([ffmpeg_path, "-i", f"tmp/video.{file_extension}", "-i", "tmp/audio.wav","-q:v", "1", "-codec", "copy", f"video.{file_extension}", "-y"])
else:
#recreate video from frames (without audio)
call([ffmpeg_path, "-r", str(fps), "-i", "frames/frame_%d.png","-q:v", "1", "-vcodec", str(codec), "-b", str(bitrate), "-pix_fmt", "yuv420p", f"video.{file_extension}", "-y"])
print("[INFO] reconstruction is finished")
To convert RGB to YUV and back:
def rgb2yuv(image_name):
frame_path = os.path.join(rgb_folder, image_name)
rgb_image = cv2.imread(frame_path)
if rgb_image is None:
print(f"Error: Unable to load {frame_path}")
return
yuv_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2YCrCb)
#yuv_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2YUV)
# Split YUV channels
Y = yuv_image[:, :, 0]
U = yuv_image[:, :, 1]
V = yuv_image[:, :, 2]
y_path = os.path.join(y_folder, image_name)
u_path = os.path.join(u_folder, image_name)
v_path = os.path.join(v_folder, image_name)
cv2.imwrite(y_path, Y)
cv2.imwrite(u_path, U)
cv2.imwrite(v_path, V)
def yuv2rgb(image_name):
y_path = os.path.join(y_folder, image_name)
u_path = os.path.join(u_folder, image_name)
v_path = os.path.join(v_folder, image_name)
Y = cv2.imread(y_path, cv2.IMREAD_GRAYSCALE)
U = cv2.imread(u_path, cv2.IMREAD_GRAYSCALE)
V = cv2.imread(v_path, cv2.IMREAD_GRAYSCALE)
if Y is None or U is None or V is None:
print(f"Error: Unable to load components for {image_name}")
return
yuv_image = np.stack((Y, U, V), axis=-1)
rgb_image = cv2.cvtColor(yuv_image, cv2.COLOR_YCrCb2RGB)
#rgb_image = cv2.cvtColor(yuv_image, cv2.COLOR_YUV2RGB)
rgb_path = os.path.join(rgb_folder, image_name)
cv2.imwrite(rgb_path, rgb_image)
Natt is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.