diff --git a/.gitignore b/.gitignore index 9f16073..09561d3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,10 @@ venv/* output/* .envrc -training_data/* \ No newline at end of file +training_data/data* +training_data/info* +training_data/training_data_*/ +training_data/*.vec +training_data/backgrounds.txt +training_data/negatives +training_data/opencv \ No newline at end of file diff --git a/Main.py b/Main.py index 0733ebd..4b03c6f 100644 --- a/Main.py +++ b/Main.py @@ -82,12 +82,7 @@ if args.file: cap = cv2.VideoCapture(args.file) else: cap = cv2.VideoCapture(0, cv2.IMREAD_GRAYSCALE) # instead of grayscale you can also use -1, 0, or 1. -faceCascade = cv2.CascadeClassifier(r"./cascades/lbpcascade_frontalface.xml") # CHECK THIS FIRST TROUBLE SHOOTING -faceCascade_default = cv2.CascadeClassifier(r"./cascades/haarcascade_frontalface_default.xml") -faceCascade_alt = cv2.CascadeClassifier(r"./cascades/haarcascade_frontalface_alt.xml") -faceCascade_alt2 = cv2.CascadeClassifier(r"./cascades/haarcascade_frontalface_alt2.xml") -faceCascade_alttree = cv2.CascadeClassifier(r"./cascades/haarcascade_frontalface_alt_tree.xml") -profileFaceCascade = cv2.CascadeClassifier(r"./cascades/haarcascade_profileface.xml") +faceCascade = cv2.CascadeClassifier(r"./cascades/cascade_10.xml") # CHECK THIS FIRST TROUBLE SHOOTING datestamp = "{:%Y_%m_%d %H_%M_%S}".format(datetime.datetime.now()) output_dir = r"./output/" + datestamp + r"/" @@ -112,51 +107,12 @@ while(True): # Detect faces in the image faces = faceCascade.detectMultiScale( gray, - scaleFactor=1.1, - minNeighbors=5, - minSize=(30, 30) + scaleFactor=1.2, + minNeighbors=2, + # minSize=(70, 90) + minSize=(200, 200) ) - if len(faces) == 0: - faces = faceCascade_default.detectMultiScale( - gray, - scaleFactor=1.1, - minNeighbors=5, - minSize=(30,30) - ) - - if len(faces) == 0: - faces = profileFaceCascade.detectMultiScale( - gray, - scaleFactor=1.1, - minNeighbors=5, - minSize=(30,30) - ) - - if len(faces) == 0: - faces = faceCascade_alt.detectMultiScale( - gray, - scaleFactor=1.1, - minNeighbors=5, - minSize=(30,30) - ) - - if len(faces) == 0: - faces = faceCascade_alt2.detectMultiScale( - gray, - scaleFactor=1.1, - minNeighbors=5, - minSize=(30,30) - ) - - if len(faces) == 0: - faces = faceCascade_alttree.detectMultiScale( - gray, - scaleFactor=1.1, - minNeighbors=5, - minSize=(30,30) - ) - # Draw a rectangle around the faces for (x, y, w, h) in faces: if args.training_data: diff --git a/quickstart.txt b/quickstart.txt index bd9f1a0..be84a99 100644 --- a/quickstart.txt +++ b/quickstart.txt @@ -17,4 +17,9 @@ Now you can run the program. It is recommended to run the program with -d and -o Training Data: -https://www.kaggle.com/datasets/utkarshsaxenadn/landscape-recognition-image-dataset-12k-images \ No newline at end of file +https://www.kaggle.com/datasets/utkarshsaxenadn/landscape-recognition-image-dataset-12k-images + + +create positives from the negatives: \opencv\build\x64\vc15\bin\opencv_createsamples.exe -img .\positives\face_1.png -bg .\bg.txt -info info/info.lst -pngoutput info -maxxangle 0.8 -maxyangle 0.8 -maxzangle 0.8 -num 1950 +Create vec files from positives: .\opencv\build\x64\vc15\bin\opencv_createsamples.exe -info .\info\info.lst -num 1950 -w 80 -h 80 -vec positives-80.vec +(I created a 20, 40, and 80) we have 1650 positives \ No newline at end of file diff --git a/training_data/TestVideo.mp4 b/training_data/TestVideo.mp4 new file mode 100644 index 0000000..ef1b312 Binary files /dev/null and b/training_data/TestVideo.mp4 differ diff --git a/training_data/create_ground_truth.py b/training_data/create_ground_truth.py new file mode 100644 index 0000000..4eb4a9b --- /dev/null +++ b/training_data/create_ground_truth.py @@ -0,0 +1,93 @@ +import cv2 +import sys + +(major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.') + +if __name__ == '__main__' : + + # Set up tracker. + # Instead of MIL, you can also use + + tracker_types = ['BOOSTING', 'MIL','KCF', 'TLD', 'MEDIANFLOW', 'GOTURN', 'MOSSE', 'CSRT'] + tracker_type = tracker_types[1] + + if int(minor_ver) < 3: + tracker = cv2.Tracker_create(tracker_type) + else: + if tracker_type == 'BOOSTING': + tracker = cv2.TrackerBoosting_create() + if tracker_type == 'MIL': + tracker = cv2.TrackerMIL_create() + if tracker_type == 'KCF': + tracker = cv2.TrackerKCF_create() + if tracker_type == 'TLD': + tracker = cv2.TrackerTLD_create() + if tracker_type == 'MEDIANFLOW': + tracker = cv2.TrackerMedianFlow_create() + if tracker_type == 'GOTURN': + tracker = cv2.TrackerGOTURN_create() + if tracker_type == 'MOSSE': + tracker = cv2.TrackerMOSSE_create() + if tracker_type == "CSRT": + tracker = cv2.TrackerCSRT_create() + + # Read video + video = cv2.VideoCapture("./TestVideo.mp4") + + # Exit if video not opened. + if not video.isOpened(): + print("Could not open video") + sys.exit() + + # Read first frame. + ok, frame = video.read() + if not ok: + print('Cannot read video file') + sys.exit() + + # Define an initial bounding box + bbox = (287, 23, 86, 320) + + # Uncomment the line below to select a different bounding box + bbox = cv2.selectROI(frame, False) + + # Initialize tracker with first frame and bounding box + ok = tracker.init(frame, bbox) + + while True: + # Read a new frame + ok, frame = video.read() + if not ok: + break + + # Start timer + timer = cv2.getTickCount() + + # Update tracker + ok, bbox = tracker.update(frame) + + # Calculate Frames per second (FPS) + fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer); + + # Draw bounding box + if ok: + # Tracking success + p1 = (int(bbox[0]), int(bbox[1])) + p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3])) + cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1) + else : + # Tracking failure + cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2) + + # Display tracker type on frame + cv2.putText(frame, tracker_type + " Tracker", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2); + + # Display FPS on frame + cv2.putText(frame, "FPS : " + str(int(fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2); + + # Display result + cv2.imshow("Tracking", frame) + + # Exit if ESC pressed + k = cv2.waitKey(1) & 0xff + if k == 27 : break \ No newline at end of file diff --git a/training_data/positives/0.png b/training_data/positives/0.png new file mode 100644 index 0000000..285f983 Binary files /dev/null and b/training_data/positives/0.png differ diff --git a/training_data/positives/1.png b/training_data/positives/1.png new file mode 100644 index 0000000..ac8dd9c Binary files /dev/null and b/training_data/positives/1.png differ diff --git a/training_data/positives/2.png b/training_data/positives/2.png new file mode 100644 index 0000000..3e4d890 Binary files /dev/null and b/training_data/positives/2.png differ diff --git a/training_data/positives/3.png b/training_data/positives/3.png new file mode 100644 index 0000000..a3dc065 Binary files /dev/null and b/training_data/positives/3.png differ diff --git a/training_data/positives/4.png b/training_data/positives/4.png new file mode 100644 index 0000000..b3c4185 Binary files /dev/null and b/training_data/positives/4.png differ diff --git a/training_data/positives/5.png b/training_data/positives/5.png new file mode 100644 index 0000000..a9329dd Binary files /dev/null and b/training_data/positives/5.png differ diff --git a/training_data/positives/6.png b/training_data/positives/6.png new file mode 100644 index 0000000..497ff71 Binary files /dev/null and b/training_data/positives/6.png differ diff --git a/training_data/positives/7.png b/training_data/positives/7.png new file mode 100644 index 0000000..5c3078d Binary files /dev/null and b/training_data/positives/7.png differ diff --git a/training_data/positives/8.png b/training_data/positives/8.png new file mode 100644 index 0000000..4010fe7 Binary files /dev/null and b/training_data/positives/8.png differ diff --git a/training_data/positives/9.png b/training_data/positives/9.png new file mode 100644 index 0000000..564959e Binary files /dev/null and b/training_data/positives/9.png differ diff --git a/training_data/training_data_setup.py b/training_data/training_data_setup.py new file mode 100644 index 0000000..1e7d382 --- /dev/null +++ b/training_data/training_data_setup.py @@ -0,0 +1,120 @@ +from PIL import Image +import os +import subprocess +import shutil + +backgrounds_file_path = "backgrounds.txt" +info_base_path = r"./info" +negatives_path = r"./negatives" +positives_path = r"./positives" +training_data_base = r"./training_data_" + +opencv_path = r".\opencv\build\x64\vc15\bin\opencv_createsamples.exe" + +set_sizes = [1, 2, 5, 10] + +max_xangle = 0.5 +max_yangle = 0.5 +max_zangle = 0.5 + +w, h = 25, 18 + +class InfoEntry: + info_lst_line: str + image_path: str + + def __init__(self, info_line, file_path): + self.info_lst_line = info_line + self.image_path = file_path + + def __str__(self): + return f"Image Entry: {self.info_lst_line}, {self.image_path}" + + +max_x = 750 +max_y = 800 + +# remove too small images +for image in os.listdir("./negatives"): + im = Image.open(f"./negatives/{image}") + width, height = im.size + del im + if width <= max_x: + os.remove(f"./negatives/{image}") + elif height <= max_y: + os.remove(f"./negatives/{image}") + +# remove any existing file and assume old data +if os.path.exists(backgrounds_file_path): + os.remove(backgrounds_file_path) + +# regenerate the available negatives list +count_negatives = len(os.listdir(negatives_path)) +for img in os.listdir(negatives_path): + line = f"{negatives_path}/" + img + "\n" + with open(backgrounds_file_path, 'a') as f: + f.write(line) + +info_dirs = [] + +if len(os.listdir(positives_path)) > max(set_sizes): + print("Your set sizes were larger than the available positive images!") + quit(2) + +for img in os.listdir(positives_path): + i = len(info_dirs) + info_dir = f"{info_base_path}{i}" + + com = f"{opencv_path} -img positives/" + str(i) + ".png -bg backgrounds.txt -info " + info_dir + "/info.lst" + \ + " -pngoutput " + info_dir + " -maxxangle " + str(max_xangle) + " -maxyangle " + str(max_yangle) + " -maxzangle " + str(max_zangle) + \ + " -num " + str(count_negatives) + + if not os.path.exists(info_dir): + subprocess.call(com, shell=True) + + info_dirs.append(info_dir) + +for i in set_sizes: + if not os.path.exists(training_data_base + str(i)): + os.makedirs(training_data_base + str(i)) + +def join_info_folders(info_dirs: list, output_dir: str): + info_dir: str + cur_entry_name = 0 + for info_dir in info_dirs: + info_lines = [] + with open(info_dir + "/info.lst", 'r') as info_file: + for line in info_file.readlines(): + image_path = f"{info_dir}/{line.split(' ')[0]}" + info_lines.append(InfoEntry(line.strip(), image_path)) + + item: InfoEntry + for item in info_lines: + shutil.copy(item.image_path, f"{output_dir}/{str(cur_entry_name)}.jpg") + with open(f"{output_dir}/info.lst", 'a') as info_file: + to_write = [] + to_write.append(str(cur_entry_name) + ".jpg") + to_write = to_write + item.info_lst_line.split(" ")[1:] + to_write.append("\n") + info_file.write(" ".join(to_write)) + cur_entry_name += 1 + +for i in set_sizes: + join_info_folders(info_dirs[:i], training_data_base + str(i)) + +commands = [] + +for i in set_sizes: + num_positives = len(os.listdir(training_data_base + str(i))) + if os.path.exists(training_data_base + str(i) + ".vec"): + os.remove(training_data_base + str(i) + ".vec") + com = f"{opencv_path} -info {training_data_base + str(i)}\info.lst -num {num_positives} -w {w} -h {h} -vec {training_data_base + str(i)}.vec" + subprocess.call(com, shell=True) + commands.append(f".\opencv\\build\\x64\\vc15\\bin\opencv_traincascade.exe -data data_{str(i)} -vec .\\{training_data_base + str(i)}.vec -bg .\\{backgrounds_file_path} -numPos {num_positives} -numNeg {num_positives / 2} -numStages 15 -w {w} -h {h}") + + if not os.path.exists(".\data_" + str(i)): + os.makedirs(".\data_" + str(i)) + +for i in commands: + print(f"You are ready to train the models with: \n {i}") +