From 2882528690a5d4f24c9af4038b09c3d9ec0d2efb Mon Sep 17 00:00:00 2001 From: carl Date: Wed, 24 Jul 2024 21:44:29 -0300 Subject: [PATCH] big cleanup --- doc/yoloserv.odt | Bin 126629 -> 127722 bytes sbin/ctl.sh | 13 ++++- src/camera.py | 25 ++++----- src/face_recognitionx.py | 35 ++++++------- src/faceclass.py | 73 ++++++++++++++++++++------ src/yoloserv.py | 107 +++++++++++++++++++++++++++------------ 6 files changed, 173 insertions(+), 80 deletions(-) diff --git a/doc/yoloserv.odt b/doc/yoloserv.odt index 9676f84fc5c3def136d9d8e244bc480d69f597eb..22222fa140c952a8dee690ebea8669b80bf9feb3 100644 GIT binary patch delta 20268 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

0Fdofuz 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 delta 19132 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 x|I`g5lEP0wa-YfgCm_WUd^Nq#(?IxOmjen?-d 100: - break + if pixel_avg > 150: + return '{ "status":4774, "remark":"Frame is too bright " }' + if pixel_avg < 50: + return '{ "status":4775, "remark":"Frame is too dark " }' cap.release() + return '{ "status":0, "remark":"OK", "pixel_avg":%d }' % pixel_avg - # show the raw acquired image for a test + # show the raw acquired image for a test - only works if opencv was compiled a certain way def show(self): cv2.imshow("test_window",self.frame) cv2.waitKey(0) @@ -66,19 +69,12 @@ class Camera(object): # save the image to a named file def save(self, filename, format="png"): - ret = 0 - buffer = None - if format=="png": - ret, buffer = cv2.imencode('.png', self.frame) - else: - ret, buffer = cv2.imencode('.jpg', self.frame) - if not ret: - print("Bad ret code ", ret) - cv2.imwrite(filename + "." + format, buffer) + cv2.imwrite(filename + "." + format, self.frame) + return 0 def dump(self): - print (self.frame) + #print (self.frame) return self.frame @@ -98,5 +94,6 @@ class Camera(object): if __name__ == '__main__': m = Camera() - m.acquire(0,"/tmp/newpic.jpg",True) + print(m.acquire("0")) + m.save("/tmp/test","png") diff --git a/src/face_recognitionx.py b/src/face_recognitionx.py index 48f42c927..0cfc567e0 100644 --- a/src/face_recognitionx.py +++ b/src/face_recognitionx.py @@ -42,7 +42,9 @@ import urllib.request from faceclass import FaceClass import time - +# +# This, together with its superclass, is the reference implementation - CTO +# class FaceRecognition(FaceClass): @@ -53,32 +55,30 @@ class FaceRecognition(FaceClass): face_match_json = None conf = [] - + # @doc find all the faces in the named image def get_faces(self, name): print("** get_faces ... %s" % name) try: # Get all faces from images with qualities, landmarks, and embeddings - boxes = face_recognition.face_locations(self.imgs[name], number_of_times_to_upsample=2, model='hog') - print("found %d boxes for %s" % (len(boxes), name) ) + self.boxes = face_recognition.face_locations(self.imgs[name], number_of_times_to_upsample=2, model='hog') + print("found %d boxes for %s" % (len(self.boxes), name) ) # Get numerical representation of faces (required for face match) self.encs[name] = face_recognition.face_encodings(self.imgs[name])[0] #print("encoding for %s : " % name, self.encs[name]) except Exception as ex: self.errstr = "image processing exception at get_faces: "+str(ex) - return -1 - return len(boxes) - - -# return " id=%d photo=%d result=%d " % (self.id_face, self.photo_face, len(self.image_inference_result)) + return '{ "status":222310, "remark":"image processing exception", "guilty_param":"error", "guilty_value":"%s" }' % str(ex) + return '{ "status":0, "remark":"OK", "faces":%d, "boxes":%s }' % (len(self.boxes), json.dumps(self.boxes)) + # @doc find the landmarks of the given face def get_landmarks(self, name): - self.landmarks = face_recognition.face_landmarks(self.imgs[name]) - print(self.landmarks) - return self.landmarks + landmarks = face_recognition.face_landmarks(self.imgs[name]) + return '{ "status":0, "remark":"OK", "landmarks":%s }' % json.dumps(landmarks) - def compute_scores(self, name1, name2): + # @doc compare two named images, previously loaded + def compare(self, name1, name2): print("** computing ... %s vs %s" % (name1,name2)) try: res = face_recognition.compare_faces([self.encs[name1]], self.encs[name2]) @@ -90,8 +90,9 @@ class FaceRecognition(FaceClass): self.tree["score"] = self.match_score[0] except Exception as ex: self.errstr = "image comparison exception at compute_scores: "+str(ex) - return -1 - return self.match_score + return '{ "status":332410, "remark":"%s" }' % self.errstr + return '{ "status":0, "remark":"OK", "score":%d }' % self.match_score[0] + def get_scores(self): return json.dumps(self.tree) @@ -99,7 +100,7 @@ class FaceRecognition(FaceClass): def detect_all(self): n=0 - self.load("localcam","regula", "/tmp/localcam.png", "/tmp/regula/Portrait_0.jpg") + self.load("localcam", "/tmp/localcam.png", "regula", "/tmp/regula/Portrait_0.jpg") self.get_faces("localcam") self.get_faces("regula") print("computed ... ", d.compute_scores("regula","localcam")) @@ -115,7 +116,7 @@ if __name__ == '__main__': if sys.argv[1]=="kiosk": # lfw n=0 - d.load("localcam","regula", "/tmp/localcam.png", "/tmp/regula/Portrait_0.jpg") + d.load("localcam", "/tmp/localcam.png", "regula", "/tmp/regula/Portrait_0.jpg") d.get_faces("localcam") d.get_faces("regula") print("computed ... ", d.compute_scores("regula","localcam")) diff --git a/src/faceclass.py b/src/faceclass.py index ef2dd1c9a..23f01fc42 100644 --- a/src/faceclass.py +++ b/src/faceclass.py @@ -2,6 +2,8 @@ import sys import os import cv2 import json +import base64 + from matplotlib import pyplot as plt class FaceClass(object): @@ -24,20 +26,20 @@ class FaceClass(object): # Load a pics using the device label def load1(self, name,fname): if not os.path.isfile(fname): - return False + return '{ "status":442565, "remark":"file name not found", "guilty_param":"fname", "guilty_value":"%s" }' % (fname) self.imgs[name] = cv2.imread(fname) - #print(" Loaded %s from file %s" % (name, fname)) - return True + print(" Loaded %s from file %s" % (name, fname)) + return '{ "status":0, "remark":"OK", "name":"%s", "fname":"%s" }' % (name,fname) - def load(self, name1,name2,fname1,fname2): + def load2(self, name1,fname1,name2,fname2): print("FaceClass loading files ....................... ") if not os.path.isfile(fname1): print("Cant access file ",fname1) - return -1 + return '{ "status":442566, "remark":"file not found", "guilty_param":"fname", "guilty_value":"%s" }' % (fname1) if not os.path.isfile(fname2): print("Cant access file ",fname2) - return -1 + return '{ "status":442567, "remark":"file not found", "guilty_param":"fname", "guilty_value":"%s" }' % (fname2) self.imfiles.append(fname1) self.imfiles.append(fname2) self.imnames[name1] = fname1 @@ -49,38 +51,79 @@ class FaceClass(object): p2.imshow(name2, self.imgs[name2]) p1.show() print("FaceClass: Loaded %s from file %s" % (name1, fname1)) - return 1 + print("FaceClass: Loaded %s from file %s" % (name2, fname2)) + return '{ "status":0, "remark":"OK", "name1":"%s", "fname1":"%s", "name2":"%s", "fname2":"%s" }' % (name1,fname1,name2,fname2) - def box(self, name, x, y, w, h): - cv2.rectangle(self.imgs[name],(x,y),(x+w,y+h), (0,255,0), 4) + # @doc draw a box and save with a new name + def rect(self, name, x1, y1, x2, y2, newname): + self.imgs[newname] = cv2.rectangle(self.imgs[name],(x1,y1),(x2,y2), (0,255,0), 4) + # @doc crop an image and save with a new name + def crop(self, name, x1, y1, x2, y2, newname): + print(x1,y1,x2,y2) + self.imgs[newname] = self.imgs[name][x1:x2, y1:y2] # Load the config def init(self): with open("/etc/ukdi.json","r") as f: self.conf = json.loads(f.read()) - # Find all faces def get_faces(self,name): - return -1 + return '{ "status":88241, "remark":"override this!" }' # Find best face def get_best_face(self): - return None + return '{ "status":88242, "remark":"override this!" }' + + # @doc find the biggest face in the named image + def get_ideal(self, name): + self.boxes = [] + self.get_faces(name) + found = -1 + ix = 0 + biggest = -1 + for b in self.boxes: + print(json.dumps(b)) + area = b[3] * b[2] + print(b,area) + if area > biggest: + found = ix + biggest = area + # box returns (left,top,right,bottom) - ffs + #self.crop(name,b[1],b[0],b[3],b[2],"_crop") + #self.rect(name,b[0],b[1],b[2],b[3],"_rect") + # rect expects x1,y1,x2,y2 + self.rect(name,b[0],b[1],b[1]+b[3],b[0]+b[2],"_rect") + ix+=1 + if found < 0: + return '{ "status":8572421, "remark":"no ideal face", "guilty_param":"name", "guilty_value":"%s" }' % name + return '{ "status":0, "remark":"OK", "faces":%d, "ideal_ix":%s, "ideal_area":%d, "boxes":%s }' % (len(self.boxes), found, biggest, json.dumps(self.boxes)) + + + # return a base64 version of the pic in memory + def dump64(self,which,format="png"): + if format=="jpg": + _ , im_arr = cv2.imencode('.jpg', self.imgs[which]) + img_as_txt = base64.b64encode(im_arr) + return b'data:image/jpeg;base64,'+img_as_txt + _ , im_arr = cv2.imencode('.png', self.imgs[which]) + img_as_txt = base64.b64encode(im_arr) + return b'data:image/png;base64, '+img_as_txt + # Find landmarks def get_landmarks(self): - return '{ "status": 9999 }' + return '{ "status":88243, "remark":"override this!" }' # Find metadata (age etc) def get_metadata(self,name): - return None + return '{ "status":88244, "remark":"override this!" }' # Match two faces def process(self,name1,name2): - return None + return '{ "status":88245, "remark":"override this!" }' diff --git a/src/yoloserv.py b/src/yoloserv.py index f024f851a..87ad8f586 100644 --- a/src/yoloserv.py +++ b/src/yoloserv.py @@ -23,7 +23,7 @@ class yoloserv(object): yolo = None device = None - imgdir = None + indir = None outdir = None facedetector = None lifedetector = None @@ -50,7 +50,7 @@ class yoloserv(object): print("Init yoloserv: %s @ %s %s " % (self.conf["yolo_devices"], self.conf["yolo_indir"], self.conf["yolo_outdir"]) ) self.devices = self.conf["yolo_devices"].split(",") - self.imgdir = self.conf["yolo_indir"] + self.indir = self.conf["yolo_indir"] self.outdir = self.conf["yolo_outdir"] # Object (face) matching @@ -65,7 +65,7 @@ class yoloserv(object): self.facematcher = Deepfacex() self.facematcher.init("dlib","SFace") if "face_recognition" in self.devices: - print("Loading deepface facematch...") + print("Loading face_recognition facematch...") from face_recognitionx import FaceRecognition self.facematcher = FaceRecognition() self.facematcher.init() @@ -123,55 +123,79 @@ class yoloserv(object): self.intox_detector.init() + # Acquire image - the device used depends on the device list and what actual file was loaded as self.camera + # @doc acquires an image from the camera (test OK CG 2024-0724) @cherrypy.expose def svc_acquire(self,camidx=0): self.camera.acquire(camidx) - return "0 OK" + return '{ "status":0, "remark":"OK" }' - # Test + # Test- (opencv) @cherrypy.expose def svc_show(self): self.camera.show() - return "0 OK" + return '{ "status":0, "remark":"OK" }' + # @doc saves the camera image to a file (test OK CG 2024-0724) @cherrypy.expose def svc_save(self,filename,extn="png"): - self.camera.save(filename,extn) - return "0 OK" + self.camera.save(self.outdir + filename,extn) + return '{ "status":0, "remark":"OK", "outfile": "%s/%s.%s" }' % (self.outdir,filename,extn) + # @doc dumps the camera image as an array (test OK CG 2024-0724) @cherrypy.expose def svc_dump(self): buf = self.camera.dump() return buf + # @doc dumps the camera image as a base64 encoded array (test OK CG 2024-0724) @cherrypy.expose def svc_dump64(self): - buf = self.camera.dump64() + buf = self.camera.dump64().decode() return buf + # @doc dumps the camera image as an tag for straight to HTML output (test OK CG 2024-0724) + @cherrypy.expose + def svc_dumphtml(self): + buf = self.camera.dump64().decode() + return "" % buf + + # Find faces - the algorithm used depends on the device list and what actual file was loaded as self.camera - # Simply load an image + # @doc load an image from a file using the specified yoloserv plugin (test OK CG 2024-0724) @cherrypy.expose - def svc_load_face(self,infile): - self.facematcher.load(infile) + def svc_load_face(self,name,infile): + return self.facematcher.load1(name, self.indir + infile) + # @doc load images from two files using the specified yoloserv plugin (test OK CG 2024-0724) @cherrypy.expose - def svc_detect_faces(self,infile): - nfaces = self.facematcher.detect_all(self.imgdir + infile) - return '{ "status":0, "remark":"found faces", "count":%d }' % (nfaces) + def svc_load_faces(self,name1,infile1,name2,infile2): + return self.facematcher.load2(name1, self.indir + infile1, name2, self.indir + infile2) - # Find the most prominent object + # @doc find all the faces in the named image that was loaded using the above calls (test OK CG 2024-0724) @cherrypy.expose - def svc_ideal_face(self,infile): - self.facematcher.detect_one(infile) + def svc_get_faces(self,which): + return self.facematcher.get_faces(which) + + # @doc find the most prominent face in the named image that was loaded using the above calls (test OK CG 2024-0724) + # you can access the new ideal face (if present) with the image name "_ideal" + @cherrypy.expose + def svc_get_ideal(self,which): + return self.facematcher.get_ideal(which) + + # @doc dumps the named image as an tag for straight to HTML output (test OK CG 2024-0724) + @cherrypy.expose + def svc_imgtag(self,which): + buf = self.facematcher.dump64(which).decode() + return "" % buf # Match faces together @cherrypy.expose - def svc_match_faces(self,infile1,infile2): - return self.facematcher.detect(infile1,infile2) + def svc_compare(self,name1,name2): + return self.facematcher.compare(name1,name2) @cherrypy.expose @@ -180,8 +204,24 @@ class yoloserv(object): os._exit(0) + + + + # @doc find the landmarks in the named image (test OK CG 2024-0724) @cherrypy.expose - def facematch(self,dev1,dev2): + def svc_get_landmarks(self,which): + return self.facematcher.get_landmarks(which) + + # Match faces together + @cherrypy.expose + def svc_match_faces(self,infile1,infile2): + return self.facematcher.detect(infile1,infile2) + def json2obj(self,jsonx): + return json.laods(jsonx) + + # @doc put all the steps for a retail facematch into one convenient functions + @cherrypy.expose + def retail_facematch(self,dev1,dev2): if self.facematcher is None: return '{ "status":777244, "remark":"suitable yolo_device" }' @@ -199,15 +239,18 @@ class yoloserv(object): if self.conf["emulate_facematch"]: return '{ "status":0, "remark":"OK", "data":{} }' - status = self.facematcher.load(dev1, dev2, img1, img2) - if not status: - return '{ "status":777242, "remark":"face loading failed", "guilty_param":"facematch", "guilty_value":"%s" }' % (status) - if self.facematcher.get_faces(dev1) < 1: - return '{ "status":777243, "remark":"face loading failed", "guilty_param":"image", "guilty_value":"%s" }' % (dev1) - if self.facematcher.get_faces(dev2) < 1: - return '{ "status":777244, "remark":"face loading failed", "guilty_param":"image", "guilty_value":"%s" }' % (dev2) - if self.facematcher.compute_scores(dev1,dev2) < 1: - return '{ "status":777245, "remark":"face matching failed", "guilty_param":"image", "guilty_value":"%s" }' % (self.facematcher.errstr) + obj = self.json2obj(self.facematcher.load_faces(dev1, img1, dev2, img2)) + if obj["status"] > 0: + return jsonx + obj = self.json2obj(self.facematcher.get_faces(dev1)) + if obj["status"] > 0: + return jsonx + obj = self.json2obj(self.facematcher.get_faces(dev2)) + if obj["status"] > 0: + return jsonx + obj = self.json2obj(self.facematcher.compute_scores(dev1,dev2)) + if obj["status"] > 0: + return jsonx jsonstr = self.facematcher.get_scores() return '{ "status":0, "remark":"OK", "data": %s }' % (jsonstr) @@ -360,13 +403,13 @@ class yoloserv(object): if __name__ == '__main__': # Deal with the incoming call parameters servport = int(sys.argv[1]) - imgdir = sys.argv[2] + indir = sys.argv[2] outdir = sys.argv[3] # Initialise the webserver s = yoloserv() s.initialise() - #s.initialise(imgdir,outdir,weightsfile) + #s.initialise(indir,outdir,weightsfile) cherrypy.config.update({'server.socket_host': '0.0.0.0', 'server.socket_port': servport}) cherrypy.quickstart(s, '/')