Introduction: Haunted Magic Mirror

For a while now I have wanted to make a Magic Mirror. I decided to make a haunted one for Halloween that would follow people walking by my desk at work.

Parts I used:



1. Raspberry Pi 2 or 3
2. SD Card with Raspbian Lite
3. Pi Camera
4. Old LCD Monitor - Preferably with HDMI
5. Acrylic see-through Mirror
6. 8' of Molding
7. 8' of 4x1 Wood
8. Power Cables(2)
9. HDMI Cable
10. Ghost pictures (36)
11. Spray paint

1. Hammered Copper
2. Sea Glass
3. Black
4. Clear Coat





Tools I used:



• Wood glue
• Clamps
• Miter saw
• Table saw
• Router


Step 1: Build the Frame

You will first need to remove the LCD panel from the plastic case. Once you get the monitor disassembled measure the outside edges.

  1. Use the router to cut a groove along the entire inside edge of the baseboard. I did this to get an even edge along the entire edge of the board. The depth of the cut should be the width of the mirror so that it doesn't move once the LCD is behind it.
  2. Cut the baseboard on a 45 degree. The top and bottom pieces inside edge should be 1 1/2 inches longer than the screen. I made the sides 3 inches longer so that I could hide the camera behind the mirror.
  3. Use wood glue and corner clamps to assemble the frame.

Now we need to create back pieces to cover the Electronics and hold everything in..

  1. Cut 4 pieces of the 4x1 boards so that they will lineup and allow the LCD panel to fit snugly.
  2. Once you have the 4 boards cut use the wood glue and corner clamps to attach them together.
    1. I placed the LCD into it position so that the Board were square and then removed them once the corner clamps were in place.

    2. I chose to glue them down to the baseboard at this time and used regular clamps to hole them in place.
  3. I also glued a board across the bottom to hold down the bottom of the monitor assembly.

Step 2: Paint the Frame

My theme this year is recovered sunken treasure so I wanted a cooper covered in green patina look.

  1. I covered the frame in a solid covering of Hammered Copper.
  2. I sprayed a light coating of Black.
  3. Before the Black had a chance to dry I used the Acetone to remove the Black from the high areas. You want to do this lightly so that you do not remove the Copper coat underneath. I used a couple of spare pieces of baseboard to test the right amount of black to use and remove.
  4. I sprayed the Sea Glass onto a spare piece of cardboard and used a paintbrush to dab the green onto the frame. If you feel like you used too much of the green you can dab the copper on top of the green to remove it.
  5. I then applied a Clear Coat to seal everything in.

Step 3: Put the Mirror Together

Once the frame is dried I cut the mirror to fit inside the grooves I cut with the router. Now we need to assemble the components.

  1. I cut a strip of black vinyl to blackout the part of the mirror that is not part of the Monitor where the Camera will be sitting.
  2. I cut a hole in the vinyl where the camera will go.
    • After testing the motion tracking the camera could not see very will through the mirror so I used some acetone and a Q-tip to remove the reflective coating around the hole.
  3. Place the LCD panel and its controls into the Mirror.
    • I cut a crossbar for the top to hold the LCD panel in. I used pocket screws to hold it into place.
  4. I used adhesive Velcro and masking tape to hold the Pi, camera, and cables down.

Step 4: The Code

Here is the code for ghost_2.0.py.

Note that python is a very specific TAB syntax you will need to use the included file.

import picamera.array

import picamera

import cv2

import numpy as np

import os

from imutils.object_detection import non_max_suppression

from imutils import paths

import imutils

import time

import pygame

from pygame.locals import *

import sys

import random

### ESC key to Exit Program ###

def event_handler():

for event in pygame.event.get():

if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):

pygame.quit()

sys.exit()

### The Main Loop ###

def main():

# setup for Camera and OpenCV

### Start the Camera ###

camera = picamera.PiCamera()

stream = picamera.array.PiRGBArray(camera, size=(640, 480))

### set the resolution I used 640x480 so I could use 36 Pictures ###

camera.resolution = (640, 480)

### Set frame rate at 16 to reduce the stress on the Raspberry Pi ###

camera.framerate = 16

### set the inital values of the tracking variables ###

motionCounter = 0

avg = None

cntSleep = 0

imgNum = 1

# setup for Pygame

### import the Ghost images. You can change the info here. ###

### if you have have a differnt number of Pictues change the xrange ###

Ghost = list([pygame.image.load('Ghost/Ghost_{0}.jpg'.format(i)) for i in xrange(1,36)])

CLOCK = pygame.time.Clock()

### This is to setup the Gui-less display ###

driver = 'fbcon'

os.putenv('SDL_VIDEODRIVER', driver)

### initialize pygame ###

pygame.display.init()

size = (pygame.display.Info().current_w, pygame.display.Info().current_h)

### display the size of the screen ###

print("Framebuffer size: %d x %d" % (size[0], size[1]))

### Turn off the Mouse ###

pygame.mouse.set_visible(0)

### set the Dsiplay to Fullscreen ###

DS = pygame.display.set_mode(size, pygame.FULLSCREEN)

### Initially fill the sceen Black ###

DS.fill((0,0,0))

pygame.display.update()

#main while loop

while cv2.waitKey(1) != 27:

### Capture the stream from the camera ###

for f in camera.capture_continuous(stream, format='bgr', use_video_port=True):

### Check for ESC ###

event_handler()

### Capture the image and compare it to the previous image ###

imgOriginal = f.array stream.truncate(0)

imgOriginal = imutils.resize(imgOriginal, width=500)

imgGray = cv2.cvtColor(imgOriginal, cv2.COLOR_BGR2GRAY)

imgGray = cv2.GaussianBlur(imgGray, (21, 21), 0)

if avg is None:

avg = imgGray.copy().astype("float")

stream.truncate(0)

continue

cv2.accumulateWeighted(imgGray, avg, 0.5)

frameDelta = cv2.absdiff(imgGray, cv2.convertScaleAbs(avg))

thresh = cv2.threshold(frameDelta, 5, 255, cv2.THRESH_BINARY)[1]

thresh = cv2.dilate(thresh, None, iterations=2)

### Motion Tracking ###

(_, cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

for c in cnts:

if cv2.contourArea(c) < 5000:

continue

### The bounds of the Changed area ####

(x, y, w, h) = cv2.boundingRect(c)

if x > 0:

### Take the x cordinate add half of the Width ###

### This gets the center of the motion on the x-axis ###

### Divde by 18 and add 1 to get the Picture number ###

### Int() make it a whole number Round() rounds down ###

z = int(round((x + (w/2))/18)) + 1

### Rather than allow the Ghost to jump from center ###

### we have it cycle through till we hit new center ###

while imgNum <> z:

### If it is new motion just diplay the fist ###

if imgNum == 0:

imgNum = z

### Debug ###

#print("Picture Start = Ghost_" + str(imgNum) + ".jpg")

DS.blit(Ghost[imgNum], (336, 0))

### If its to the right go up 1 image ###

elif z > imgNum:

imgNum += 1

### Debug ###

#print("Picture + Ghost_" + str(imgNum)+ ".jpg")

DS.blit(Ghost[imgNum], (336, 0))

### if its to the left go down 1 image ###

else:

imgNum -= 1

### Debug ###

#print("Picture - Ghost_" + str(imgNum) + ".jpg")

DS.blit(Ghost[imgNum], (336, 0))

pygame.display.update()

### Debug ###

#print("Picture = " + str(z) + ", x = " + str(x) + ", w = " + str(w))

cntSleep = 0

continue

### Make the Ghost Disapear after 10 rounds with no Movement###

if cntSleep > 10:

### Debug ###

#print("Black Screen")

DS.fill((0,0,0))

pygame.display.update()

### Set the next movement to the First movement ###

imgNum = 0

### Debug ###

#print("Sleep = " + str(cntSleep))

cntSleep += 1

### Debug ####

#timerCnt += 1

#print("Timer = " + str(timerCnt))

#if timerCnt >= 100:

# quit()

return

if __name__ == "__main__": main()

Step 5: The Ghost

You now need to capture your subject.

  1. Apply makeup to your subject. I chose a White ghoul make up scheme hoping it would show up better behind the mirror.
  2. I took a bunch of picture while the subject was looking slowly from left to right. I ended up using 36 pictures.
  3. If your black background is not even like mine there will be some post production. I used Pixlr and Gimp to blackout the background.
  4. You will want the images turned 90 degrees to the left so it they will be right side up in the frame.
  5. Name the images Ghost_1.jpg to Ghost_36.jpg looking to the right (1) to the left side (36) of the frame.

Step 6: Installing the Software

I am assuming you now how to get Raspbian onto the Raspberry Pi so I will not be covering it in the tutorial. If you need help you can use this tutorial. I used the lite version to avoid the overhead of the gui. Used the following software:

  • python 2 - already installed
  • picamera
  • OpenCV
  • pygame - should already be installed in Wheezy

Before we get to installing the software we need to enable a few items in the raspi-config.

sudo raspi-config

  1. Item 5 - Enable the PiCamera module.
  2. Item 7 - Enable Overclocking.
    • This will come in handy when installing OpenCV.

I used the tutorial by Adrian at PyImagesearch to install OpenCV. I did skip the virtual environments because I could not get it working with sudo which is required to display images without a GUI. I also skipped the Python 3 steps because everything worked with the python already installed within Raspbian. You know it worked if you can so the following without an error.

sudo python

import cv2


Once you get OpenCV installed and working you need to install the python-pycamera module.

sudo apt-get update
sudo apt-get install python-picamera


The python module for pygame should already be installed you can test that everything is working together with following commands. If you dont get any errors then everything is working.

sudo python

import cv2

import picamera

import pygame


If pygame errors out try the following commands.

sudo apt-get update

sudo apt-get install python-pygame


Step 7: Setup and Copy the Files

You will need to copy the files and setup the folders.

  1. Copy python files to /home/pi/ on the Pi. Files Needed
    • ghost_2.0.py
  2. Make the folder for the Images
    • cd ~
    • mkdir Ghost
  3. Copy the images from another computer to the /home/pi/Ghost folder
  4. Test the script and make sure it runs.
    • sudo python ghost_2.0.py
  5. If the file works then create the launcher script to start on boot up. I used this tutorial.

Thank you for Viewing this tutorial. Have fun creating.

Halloween Decor Contest 2016

Participated in the
Halloween Decor Contest 2016