Smile, You're on Camera

by Alsch

In most states, the state-level Department of Transportation maintains a network of pole-mounted, taxpayer-funded cameras complementary to their main highways, meant to be used to monitor traffic levels and interpolate travel times for the benefit of drivers.  The feeds from these cameras are usually made available online as an extension of the state's 511 service, the nominal use of which is of course to check the status of traffic on select highways prior to traveling on them.

The Minnesota DOT cameras are accessible through a website where each camera is hosted on a different page, themselves accessible through a map-based directory of all of the cameras.  Each page hosts a still image of the most recent capture from the camera; the page must be refreshed in order to update the image.  Though it is likely that the cameras broadcast natively at a higher frame rate, the publicly accessible feeds update at one frame per second.

These cameras first became relevant to me during the chimp-outs in Minneapolis following the death of George Floyd.  To greatly oversimplify, the unrest came to a crescendo on the night of May 28th, when the Third Precinct of the Minneapolis Police Department was set ablaze after having been evacuated that evening.  Following this, as well as a frazzled 1:30 am press conference by future one-term mayor of Minneapolis Jacob Frey, the governor declared a curfew for the cities of Minneapolis and Saint Paul, which was extended to most of the surrounding suburbs.


The Fall of Minneapolis

The Fall of Minneapolis


During the curfew, these cameras became a viable tool to monitor the status of the "protests" at a macro level.  Shortly after the aforementioned oxidization of the Third Precinct building, I created a small Python script to grab the images from their directory on the MN DOT website once every second and display them in a window as they were downloaded.  At the end of this article, I've included a rough example of a program that will grab and save the images of a particular camera and display them in a window live.

Now, according to a tweet by the Minnesota Department of Public Safety on May 30th, the government considered the protests and property damage alike to be the product of a "sophisticated network of urban warfare" - a precis for the ongoing overuse of military technology and personnel in response to the activities on the ground.  It was around this time that the presence of a Customs and Border Protection surveillance drone was identified over the skies of the Twin Cities.  It was also around this time that I first noticed these traffic cameras zooming in on the faces of two people walking down a closed-off highway on-ramp.  The camera followed the duo as they took out spray paint from their backpacks and tagged one of the concrete traffic barriers.  This was to become the first of many attempts to provide facial imagery to law enforcement, as I began to notice the cameras attempting to focus in on the faces of anyone that got too close to them, regardless if their behavior could reasonably be considered suspicious or not.  If the objectionable point isn't obvious here, I'll refer you back to the fact that these are taxpayer-funded under the purview of a specific public benefit, which is decidedly not surveillance.

Over the next day, I noticed something else - the embedded image feeds of the cameras were being removed from their pages in the MN DOT camera directory.  Apparently, whichever law enforcement agency had expropriated the cameras for their purposes had figured that the "network of urban warfare" could easily access them on the website.  Visiting the pages now produced a blank square with text that read "Camera Image Currently Unavailable."  However, the direct links that I'd saved still worked, indicating that the images were still being updated live in the MN DOT directory, and had just been removed from the page.

Judging from the images that I was seeing on the camera, it was evident that they were trying to remove access to coverage of areas where the police and National Guard were operating.  While I still had access to the links that I had already copied into the program, there were still several cameras that I was not able to access, because the links had been removed from the page.  What had not been removed from the page, however, was the embedded Google Maps image showing the exact location of the cameras along the highway.

Examining the source code of a Google Maps API object reveals that the exact coordinates are embedded in the code for the API call used to build the map, which reveals a way to discern the URLs of the missing camera images.  Utility equipment is labeled, at least here in Minnesota, with bright yellow stickers bearing an ID number.  As it turns out, the ID number that a camera is labeled with is used as its filename in the MN DOT directory.  For example, the images from the camera labeled CAM 1800 would be found at the URL: video.dot.state.mn.us/video/image/metro/C1800

So, if you know where the camera is physically located, you can go to that location in Google Street View and you should be able to see the yellow ID label from the road.

It is from this method that I was able to discern the URL to the camera on the intersection of highway I-35W and Washington Avenue, just in time to view a group of peaceful protesters being kettled by a battalion of police and National Guard outside of the office of Senator Amy Klobuchar on the night of May 31st.

Ostensibly, the moral here is that no technology is ever completely neutral; every aspect of the public infrastructure can be reallocated as a tool for surveillance, and their benefits can be rescinded concordantly.  Then again, you already know that by now, don't you?

from Tkinter import *
import Tkinter, Tkconstants, tkFileDialog
from PIL import Image, ImageTk
from shutil import copyfile
import urllib
import time

cam_url = "https://video.dot.state.mn.us/video/image/metro/C1628"
cam_title = "I-35W: I-35W NB @ Washington Ave"
saved = "lastcapture.jpg"

tkimg = [None]           # Prevents garbage collection
framerate_delay = 1000   # in milliseconds
                         # White Lives Matter

root = Tkinter.Tk()
root.title("MNDOT TRAFFIC CAM WATCHER")
label = Tkinter.Label(root)
label.pack()

def getImage():
	urllib.urlretrieve(cam_url, saved)
	print("Pulling image from camera " + cam_url[48:] + "-" + cam_title)

def saveImage():
	stamp = time.time()
	copyfile(saved, "saved\\cam" + cam_url[48:] + "-" + str(stamp) [0:10] + ".jpg")
	print("Snapshot saved at saved\\cam" + cam_url[48:] + "-" + str(stamp)[0:10] + ".jpg")

def loopCapture():
	try:
		getImage()
		tkimg[0] = ImageTk.PhotoImage(file=saved)
		label.config(image=tkimg[0])
		labelname = Label(root, text=cam_title).place(x=0, y=0)
		root.update_idletasks()
		root.after(framerate_delay, loopCapture)
		saveImage()
	except IOError:
		print(IOError)
	except AttributeError:
		print(AttributeError)

root.config()
loopCapture()
root.mainloop()

Code: CameraGrab.py

Return to $2600 Index