arianvanp
today at 10:44 AM
"find out with opencv what the hidden message is."
Skill issue on promoter side.
Fable oneshotted it for me.
"""
Reveal a motion-camouflaged message hidden in video noise.
How it works:
The background noise scrolls vertically at a constant rate (a few px/frame),
while the noise inside the letters does not follow that motion. Any single
frame looks like pure static. The decode is:
1. Estimate the background's global motion between consecutive frames
with phase correlation (this is the "optical flow" step - the motion
is a pure translation, so one global vector suffices).
2. Motion-compensate: shift frame t+1 back by that vector so the
background lines up with frame t.
3. Take the absolute difference. The background cancels almost
perfectly; the letters (which don't move with the background)
light up.
4. Average the residual over a SHORT window of consecutive frame pairs
(long windows smear the letters, because the text itself drifts
slowly over time), blur lightly, and threshold with Otsu.
Usage:
python reveal_hidden_message.py input.mp4 [output.png]
"""
import sys
import cv2
import numpy as np
PAIRS = 5 # number of consecutive frame pairs to average (keep small!)
BLUR_SIGMA = 6 # spatial blur of each residual, in pixels
START_FRAME = 0 # where in the video to start
def load_gray_frames(path, count):
cap = cv2.VideoCapture(path)
frames = []
while len(frames) < count:
ok, frame = cap.read()
if not ok:
break
frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY).astype(np.float32))
cap.release()
if len(frames) < 2:
raise SystemExit("Could not read enough frames from the video.")
return frames
def main():
if len(sys.argv) < 2:
raise SystemExit(__doc__)
src = sys.argv[1]
dst = sys.argv[2] if len(sys.argv) > 2 else "revealed_message.png"
frames = load_gray_frames(src, START_FRAME + PAIRS + 1)
h, w = frames[0].shape
acc = np.zeros((h, w), np.float32)
for i in range(START_FRAME, START_FRAME + PAIRS):
a, b = frames[i], frames[i + 1]
# 1) global background motion between the two frames
(dx, dy), response = cv2.phaseCorrelate(a, b)
dxi, dyi = int(round(dx)), int(round(dy))
print(f"pair {i}: background shift = ({dx:+.2f}, {dy:+.2f}) px, "
f"response = {response:.2f}")
# 2) motion-compensate frame b by integer (dxi, dyi), then
# 3) residual = |a - b_shifted| on the overlapping region
ys = slice(max(0, -dyi), min(h, h - dyi))
xs = slice(max(0, -dxi), min(w, w - dxi))
ysb = slice(max(0, dyi), min(h, h + dyi) if dyi < 0 else h)
# simpler: crop both to the common overlap
a_ov = a[max(0, -dyi):h - max(0, dyi), max(0, -dxi):w - max(0, dxi)]
b_ov = b[max(0, dyi):h - max(0, -dyi), max(0, dxi):w - max(0, -dxi)]
resid = cv2.GaussianBlur(np.abs(a_ov - b_ov), (0, 0), BLUR_SIGMA)
acc[:resid.shape[0], :resid.shape[1]] += resid
# 4) normalize + Otsu threshold + light cleanup
u8 = cv2.normalize(acc, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
_, mask = cv2.threshold(u8, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
out = 255 - mask # black text on white
cv2.imwrite(dst, out)
print(f"wrote {dst}")
# optional: OCR if pytesseract is installed
try:
import pytesseract
text = pytesseract.image_to_string(out, config="--psm 6").strip()
print("OCR result:\n" + text)
except ImportError:
pass
if __name__ == "__main__":
main()