Created
June 6, 2020 18:47
-
-
Save ChuckCartledge/4e152f618d2e2174c4f2e3590f659fc6 to your computer and use it in GitHub Desktop.
Python based Haar detector to create image-map (see http://www.clc-ent.com/TBDE/Docs/faces.pdf)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://stackoverflow.com/questions/30505150/how-to-scale-up-picture-in-python | |
# https://www.techbeamers.com/python-switch-case-statement/ | |
from PIL import Image | |
import cv2 as cv | |
import sys | |
import getopt | |
import json as js | |
from tempfile import NamedTemporaryFile | |
def setffGlobals(): | |
returnValue = { | |
"scaler": 0.1, | |
"scaleFactor": 1, | |
"HaarFile": "./haarcascade_frontalface_default.xml", | |
"htmlFile": "./temp.html", | |
"inFile": "./ws-dl-group-1.jpeg", | |
"markedUpFile": "./tempMarkedUp.jpg", | |
"monitorFile": "./tempMonitoring.jpg" | |
} | |
return(returnValue) | |
def dumpffGlobals(): | |
print("The current values in 'ffGlobals' are:") | |
for attribute, value in ffGlobals.items(): | |
print('{} : {}'.format(attribute, value)) | |
def serviceCommandLineArgs (myopts, args): | |
options = { | |
"-?": "Print program usage, and exit.", | |
"-D": "Show current ffGlobals values.", | |
"-i": "Set name of input image.", | |
"-o": "Set name of output HTML file.", | |
"-H": "Set name of Haar cascade file.", | |
"-m": "Set name of image monitoring file.\n\tFile will be deleted when program exits.", | |
"-M": "Set name of image marked up file.\n\tFile will be deleted when program exits.", | |
"-s": "Set ratio of image scaling.", | |
"-S": "Save the current ffGlobale values to this file.", | |
"-L": "Load ffGlobals from this file.", | |
"-R": "Reset ffGlobals to default values." | |
} | |
for o, a in myopts: | |
global ffGlobals | |
if (o == "-?"): | |
print("usage: findFaces.py") | |
print("usage: findFaces.py [-?]") | |
print("usage: findFaces.py [-option [optionArgument]]") | |
print("usage: findFaces.py [-option [optionArgument] -option [optionArgument]]") | |
print("usage: findFaces.py [-option [optionArgument] -option [optionArgument] -option [optionArgument]") | |
print("and so on.") | |
print("Options are processed in the order they are read from left to right.") | |
print("Options are:") | |
for attribute, value in options.items(): | |
print('{} : {}'.format(attribute, value)) | |
exit() | |
elif (o == "-D"): | |
dumpffGlobals() | |
elif (o == "-i"): | |
ffGlobals["inFile"] = a | |
elif (o == "-o"): | |
ffGlobals["htmlFile"] = a | |
elif (o == "-H"): | |
ffGlobals["HaarFile"] = a | |
elif (o == "-m"): | |
ffGlobals["monitorFile"] = a | |
elif (o == "-M"): | |
ffGlobals["markedUpFile"] = a | |
elif (o == "-s"): | |
ffGlobals["scaler"] = float(a) | |
elif (o == "-S"): | |
f = open (a, "w") | |
f.write(js.dumps(ffGlobals) + "\n") | |
f.close() | |
elif (o == "-L"): | |
f = open (a, "r") | |
ffGlobals = js.loads(f.readline()) | |
f.close() | |
elif (o == "-R"): | |
ffGlobals = setffGlobals() | |
def switch(character): | |
def up(): | |
return (1 + ffGlobals["scaler"]) | |
def down(): | |
return (1/(1 +ffGlobals["scaler"])) | |
def default(): | |
return (1) | |
cases={ | |
'+':up, | |
'-':down | |
} | |
return cases.get(character, default)() | |
def htmlHeader(htmlFile, imageFile): | |
temp=Image.open(imageFile) | |
imageSize=temp.size | |
outFile = open(htmlFile, "w") | |
textList=["<html>", | |
"<title>", | |
"Now we have clickable faces.", | |
"</title>", | |
"<h1>", | |
"Now we have clickable faces.", | |
"</h1>", | |
"<br>", | |
"<body>", | |
"\n", | |
"<script>", | |
"\n", | |
"function clickmap(info) {", | |
"alert(info)", | |
"}", | |
"\n", | |
"</script>", | |
"\n", | |
"<img src=\"", | |
imageFile, | |
"\"", | |
" width=\"", | |
str(imageSize[0]), | |
"\" ", | |
"height=\"", | |
str(imageSize[1]), | |
"\" ", | |
"alt=\"faces\" ", | |
"usemap=\"#facemap\">", | |
"\n", | |
"<map name=\"facemap\">", | |
"\n" | |
] | |
outFile.writelines(textList) | |
outFile.close() | |
def htmlTrailer(htmlFile): | |
outFile = open(htmlFile, "a") | |
textList=[ | |
"</map>", | |
"\n", | |
"</body>", | |
"</html>" | |
] | |
outFile.writelines(textList) | |
outFile.close() | |
def htmlMap(htmlFile, lowerRight, upperLeft, number, scaler): | |
lowerRight=tuple(map(lambda x: int(x/scaler), lowerRight)) | |
upperLeft=tuple(map(lambda x: int(x/scaler), upperLeft)) | |
outFile = open(htmlFile, "a") | |
textList=[ | |
"<area shape='rect' coords=\"", | |
str(lowerRight[0]), | |
",", | |
str(lowerRight[1]), | |
",", | |
str(upperLeft[0]), | |
",", | |
str(upperLeft[1]), | |
"\"", | |
" href=\"#\" onclick=\"clickmap('This is some stuff about face # ", | |
str(number), | |
"')\" alt=\"", | |
str(number), | |
"\">", | |
"\n" | |
] | |
outFile.writelines(textList) | |
outFile.close() | |
def findFaces(inFile): | |
# global ffGlobals | |
face_cascade = cv.CascadeClassifier(ffGlobals["HaarFile"]) | |
im = Image.open(inFile) | |
size=im.size | |
# help(face_cascade.detectMultiScale) | |
fcScaleFactor=1.01 # works, slow, lots of false positives | |
fcScaleFactor=1.10 # works, faster, fair number of false negatives | |
fcScaleFactor=1.02 # seems like a nice balance. few false negatives, not too many false positives | |
fcScaleFactor=1.03 # seems like a nice balance. few false negatives, not too many false positives | |
fcMinNeighbors=4 | |
while True: | |
scaledSize=tuple(map(lambda x: int(ffGlobals["scaleFactor"]*x), size)) | |
om=im.resize(scaledSize, Image.ANTIALIAS) | |
f = NamedTemporaryFile() | |
om.save(f.name, "JPEG") | |
om = cv.imread(f.name) | |
savedImage=om.copy() | |
gray = cv.cvtColor(om, cv.COLOR_BGR2GRAY) | |
#calculate coordinates | |
faces = face_cascade.detectMultiScale(gray, fcScaleFactor, fcMinNeighbors) | |
print("Software has found ", len(faces), " faces (maybe).") | |
for (x,y,w,h) in faces: | |
temp=cv.rectangle(om,(x,y),(x+w,y+h),(255,0,0),2) | |
cv.imwrite(ffGlobals["monitorFile"], om) | |
inpt = input("Press + to increase, or - to decrease image size, or c to create HTML file > ") | |
inpt=inpt.lower() | |
if inpt == 'c' : | |
break | |
temp=switch(inpt) | |
ffGlobals["scaleFactor"] = ffGlobals["scaleFactor"] * temp | |
print("Input of ", inpt, "resulting in ", temp, " new scaleFactor is ", ffGlobals["scaleFactor"]) | |
return(savedImage, faces) | |
def createHTMLFile(htmlFile, inFile, savedImage, faces, scaleFactor): | |
htmlHeader(htmlFile, inFile) | |
markedUpImage=savedImage.copy() | |
counter=0 | |
for (x,y,w,h) in faces: | |
imgTemp = savedImage.copy() | |
temp=cv.rectangle(imgTemp,(x,y),(x+w,y+h),(255,0,0),5) | |
cv.imwrite(ffGlobals["monitorFile"],imgTemp) | |
inpt=input("Press Y/N/E to save/next/exit> ") | |
inpt=inpt.lower() | |
if (inpt == "y"): | |
print("Save faces[", counter, "]") | |
temp=cv.rectangle(markedUpImage,(x,y),(x+w,y+h),(255,0,0),5) | |
cv.imwrite(ffGlobals["markedUpFile"],markedUpImage) | |
inpt=input("What name goes along with face #"+str(counter)+"?") | |
htmlMap(ffGlobals["htmlFile"], [x, y], [x + w, y + h], str(counter)+" "+inpt, scaleFactor) | |
if (inpt == "e"): | |
break | |
counter=counter+1 | |
htmlTrailer(ffGlobals["htmlFile"]) | |
print("The HTML file has been created at:", ffGlobals["htmlFile"]) | |
def cleanUpTemporaryFiles(): | |
global ffGlobals | |
import os | |
filesToDelete=["markedUpFile", "monitorFile"] | |
for f in range(len(filesToDelete)): | |
file=ffGlobals[filesToDelete[f]] | |
print("Deleting: "+file) | |
os.remove(file) | |
def main(): | |
dumpffGlobals() | |
savedImage, faces = findFaces(ffGlobals["inFile"]) | |
createHTMLFile(ffGlobals["htmlFile"], ffGlobals["inFile"], | |
savedImage, faces, | |
ffGlobals["scaleFactor"]) | |
cleanUpTemporaryFiles() | |
print("The program has ended.") | |
ffGlobals = setffGlobals() | |
if __name__ == "__main__": | |
myopts, args = getopt.getopt(sys.argv[1:],"?Di:o:H:m:M:s:S:L:R") | |
serviceCommandLineArgs(myopts, args) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment