{"id":398134,"date":"2024-06-29T13:53:22","date_gmt":"2024-06-29T13:53:22","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=398134"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=398134","title":{"rendered":"<span>Blinking into Morse code<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Explaining main algorithm<\/p>\n<p>For a while I\u2019ve been thinking of writing a scientific article. I wanted it to have certain utility. <\/p>\n<p>Morse code is binary: it takes only two values \u2013 either dot (short) or hyphen (long). I figured out that short (s) can stand for two-eye blinking whilst long (l) can indicate left-eye blinking. Another question emerged: how to understand when does one-symbol recording stop? <\/p>\n<p>Empty space between two symbols can be presented by right-eye blinking \u2013 r. If I input singly symbol of short (dot) and long (hyphen), I will blink my right eye once to indicate the space between two symbols. <\/p>\n<p>To separate independent words, one has to blink her right eye twice and get rr. <\/p>\n<p>Hence, I have collected an ordered set of symbols \u2013 r, l, s, &#8212; that can be converted into a full-fledged text. Once I accomplish the transformation, I get an answer. <\/p>\n<h2>Deciphering Python Code<\/h2>\n<p>Let\u2019s take a closer look to the code functions I used.<\/p>\n<p><strong>eye_aspect_ratio(eye)<\/strong>. I use dlib library for face detection. Next, out of 68 facial parameters (these are dots that are spread across human face in a sharpened shape of face), I pick 6 that are responsible for eyes location. In Function I determine whether an eye is opened or closed by counting two Euclidean distances between the upper and lower eyelids of the eye, equally offset from the center (parameters A and B), and Euclidean distance between the right and left corners of the eye (parameter C). The bigger the number, the more open the eye is. <\/p>\n<pre><code class=\"python\">def eye_aspect_ratio(eye): \u00a0\u00a0\u00a0 A = dist.euclidean(eye[1], eye[5]) \u00a0\u00a0\u00a0 B = dist.euclidean(eye[2], eye[4]) \u00a0\u00a0\u00a0 C = dist.euclidean(eye[0], eye[3])  \u00a0\u00a0\u00a0 ear = (A + B) \/ (2.0 * C) \u00a0\u00a0\u00a0 return ear<\/code><\/pre>\n<p><strong>build_plots(name, value, plot) <\/strong>is responsible for output of an image from camera to computer screen. <\/p>\n<pre><code class=\"python\">def build_plots(name, value, plot): \u00a0\u00a0\u00a0 plot = plot.update(value) \u00a0\u00a0\u00a0 cv2.imshow(name, plot)<\/code><\/pre>\n<p>\u00a0<strong>draw_outline(frame, eye) <\/strong>takes camera frame and eyes coordinates. It then fixates eyes with neon-green rings. <\/p>\n<pre><code class=\"python\">def draw_outline(frame, eye): \u00a0\u00a0\u00a0 eyeHull = cv2.convexHull(eye) \u00a0\u00a0\u00a0 cv2.drawContours(frame, [eyeHull], -1, (0, 255, 0), 1)<\/code><\/pre>\n<p><strong>get_args() <\/strong>gets me prerequisite arguments (shape-predictor) for future execution. <\/p>\n<pre><code class=\"python\">def get_args(): \u00a0\u00a0\u00a0 ap = argparse.ArgumentParser() \u00a0\u00a0\u00a0 ap.add_argument(\"-p\", \"--shape-predictor\", required=True, help=\"path to facial landmark predictor\") \u00a0\u00a0\u00a0 args = vars(ap.parse_args()) \u00a0\u00a0\u00a0 return args<\/code><\/pre>\n<p><strong>open_video() <\/strong>opens my front camera and returns prerequisite arguments. <\/p>\n<pre><code class=\"python\">def open_video(): \u00a0\u00a0\u00a0 args = get_args() \u00a0\u00a0\u00a0 print(\"[INFO] loading facial landmark predictor\")  \u00a0\u00a0\u00a0 detector = dlib.get_frontal_face_detector() \u00a0\u00a0\u00a0 predictor = dlib.shape_predictor(args[\"shape_predictor\"])  \u00a0\u00a0\u00a0 print(\"[INFO] starting video stream thread...\") \u00a0\u00a0\u00a0 vs = VideoStream(0).start() \u00a0\u00a0\u00a0 time.sleep(1)  \u00a0\u00a0\u00a0 return vs, detector, predictor<\/code><\/pre>\n<p><strong>calibration(name, flag). <\/strong>Every person\u2019s average <em>ear<\/em> (eye aspect ratio) is different, I can\u2019t use any universal number for this parameter as former simply doesn\u2019t exist. Thus, the calibration comes at handy. It helps to get numbers for several conditions: both eyes opened, both eyes closed, left eye closed &amp; right eye opened and vice versa. After the first beep video opens and I have to hold my eyes opened till next beep. Afterwards, I close my eyes patiently waiting for last beep. Out of all sampling for eyes opened I return a minimum average value <em>ear<\/em> for left and right eyes. Out of all sampling for eyes closed I return a maximum average value <em>ear<\/em> for left and right eyes. Function<strong> <\/strong>thus returns optimal values for correct execution. <\/p>\n<pre><code class=\"python\">def calibration(name, flag): \u00a0\u00a0\u00a0 plotLeft = LivePlot(640, 360, [5, 35], invert=True) \u00a0\u00a0\u00a0 plotRight = LivePlot(640, 360, [5, 35], invert=True)  \u00a0\u00a0\u00a0 (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS[\"left_eye\"] \u00a0\u00a0\u00a0 (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS[\"right_eye\"]  \u00a0\u00a0\u00a0 vs, detector, predictor = open_video() \u00a0\u00a0\u00a0 print('\\a') \u00a0\u00a0\u00a0 time.sleep(3) \u00a0\u00a0\u00a0 time_start = time.time() \u00a0\u00a0\u00a0 both_eyes_open = [] \u00a0\u00a0\u00a0 both_eyes_close = []  \u00a0\u00a0\u00a0 while True: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 frame = vs.read() \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 frame = imutils.resize(frame, width=450) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rects = detector(gray, 0) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for rect in rects: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 shape = predictor(gray, rect) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 shape = face_utils.shape_to_np(shape)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 leftEye = shape[lStart:lEnd] \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rightEye = shape[rStart:rEnd] \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 leftEAR = int(100 * eye_aspect_ratio(leftEye)) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rightEAR = int(100 * eye_aspect_ratio(rightEye))  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if flag == 0: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 earAvg = (leftEAR + rightEAR) \/ 2.0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 both_eyes_open.append(earAvg) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 elif flag == 1: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 earAvg = (leftEAR + rightEAR) \/ 2.0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 both_eyes_close.append(earAvg)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 draw_outline(frame, leftEye) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 draw_outline(frame, rightEye)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 build_plots(\"ImagePlotLeft\", leftEAR, plotLeft) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 build_plots(\"ImagePlotRight\", rightEAR, plotRight) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cv2.imshow(name, frame)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 time_dif = time.time() - time_start \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if cv2.waitKey(25) == ord(\"q\"): \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if time_dif > 5: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 print('\\a') \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break  \u00a0\u00a0\u00a0 cv2.destroyAllWindows() \u00a0\u00a0\u00a0 vs.stop()  \u00a0\u00a0\u00a0 if flag == 0: return min(both_eyes_open) \u00a0\u00a0\u00a0 if flag == 1: return max(both_eyes_close)<\/code><\/pre>\n<p><strong>morse_code_from_eyes(). <\/strong>This is the major function of my code. In real time it monitors and analyzes human eyes. Calibration goes first and is followed by a beep sound, after which the recording starts. Recording goes the similar way as during the calibration, however, now I am comparing the results I get from camera with the ones I got from calibration one step ago. I use counter to trace one symbol per once. If I haven\u2019t utilized counter, I would have stored a large number of symbols per one blink as there wouldn\u2019t be any break. After all symbols are passed, I press the \u201cq\u201d button on a keyboard to finish recording and close front camera.<strong> <\/strong>Then function returns the result of symbols recording. <\/p>\n<pre><code class=\"python\">def morse_code_from_eyes(): \u00a0\u00a0\u00a0 both_open = calibration(\"both_eyes_open\", 0) \u00a0\u00a0\u00a0 both_close = calibration(\"both_eyes_close\", 1)  \u00a0\u00a0\u00a0 plotLeft = LivePlot(640, 360, [5, 35], invert=True) \u00a0\u00a0\u00a0 plotRight = LivePlot(640, 360, [5, 35], invert=True)  \u00a0\u00a0\u00a0 (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS[\"left_eye\"] \u00a0\u00a0\u00a0 (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS[\"right_eye\"]  \u00a0\u00a0\u00a0 vs, detector, predictor = open_video() \u00a0\u00a0\u00a0 print('\\a') \u00a0\u00a0\u00a0 time.sleep(3)  \u00a0\u00a0\u00a0 counter = 0 \u00a0\u00a0\u00a0 points = \"\"  \u00a0\u00a0\u00a0 while True: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 frame = vs.read() \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 frame = imutils.resize(frame, width=450) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rects = detector(gray, 0)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for rect in rects: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 shape = predictor(gray, rect) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 shape = face_utils.shape_to_np(shape)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 leftEye = shape[lStart:lEnd] \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rightEye = shape[rStart:rEnd] \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 leftEAR = int(100 * eye_aspect_ratio(leftEye)) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rightEAR = int(100 * eye_aspect_ratio(rightEye))  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 earAvg = (leftEAR + rightEAR) \/ 2.0  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 draw_outline(frame, leftEye) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 draw_outline(frame, rightEye)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 build_plots(\"ImagePlotLeft\", leftEAR, plotLeft) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 build_plots(\"ImagePlotRight\", rightEAR, plotRight)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if counter == 0: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if earAvg &lt;= both_close + 1: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 points += \"s\" \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter += 1 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 print(earAvg, \"ssssssssssssssssssss\") \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0elif leftEAR - rightEAR >= 0 and earAvg &lt;= both_open - 3:  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 points += \"p\" \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter += 1 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 print(leftEAR - rightEAR, earAvg, \"pppppppppppppppppp\u0440\u0440\") \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 elif rightEAR - leftEAR >= 0 and earAvg &lt;= both_open - 3: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 points += \"l\" \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter += 1 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 print(rightEAR - leftEAR, earAvg, \"llllllllllllllllllll\") \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if counter == 5: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter = 0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter += 1  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cv2.imshow(\"Frame\", frame) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if cv2.waitKey(25) == ord(\"q\"): \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break  \u00a0\u00a0\u00a0 cv2.destroyAllWindows() \u00a0\u00a0\u00a0 vs.stop() \u00a0\u00a0\u00a0 return points<\/code><\/pre>\n<p><strong>text_from_morse_code(points) <\/strong>is responsible for converting a received stroke of symbols (r,l,s) into a comprehensible text. Firstly, I save a dictionary which keys are a designation in Morse code, and the value is a letter. I split the stroke into \u201cpp\u201d to get separate independent words. Consequently, I go through all the symbols before \u201cp\u201d and convert each one into a letter. After, letters are combined into words and words into sentences. The ultimate result is a returned word. <\/p>\n<pre><code class=\"python\">def text_from_morse_code(points): \u00a0\u00a0\u00a0 alphabet = {\"sl\": \"A\", \"lsss\": \"B\", \"lsls\": \"C\", \"lss\": \"D\", \"s\": \"E\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"ssls\": \"F\", \"lls\": \"G\", \"ssss\": \"H\", \"ss\": \"I\", \"slll\": \"J\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"lsl\": \"K\", \"slss\": \"L\", \"ll\": \"M\", \"ls\": \"N\", \"lll\": \"O\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"slls\": \"P\", \"llsl\": \"Q\", \"sls\": \"R\", \"sss\": \"S\", \"l\": \"T\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"ssl\": \"U\", \"sssl\": \"V\", \"sll\": \"W\", \"lssl\": \"X\", \"lsll\": \"Y\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"llss\": \"Z\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"sllll\": \"1\", \"sslll\": \"2\", \"sssll\": \"3\", \"ssssl\": \"4\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"sssss\": \"5\", \"lssss\": \"6\", \"llsss\": \"7\", \"lllss\": \"8\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"lllls\": \"9\", \"lllll\": \"0\"}      points = points.split(\"pp\")     answer = \"\"     for word in points:         letters = word.split(\"p\")         new_word = \"\"         for letter in letters:             if letter in alphabet:                 new_word += alphabet[letter]             else:                 new_word += \"-\"         answer += new_word + \" \"     return answer<\/code><\/pre>\n<h2>Conclusion<\/h2>\n<p>First you need to run the morse_code_from_eyes() function, and save the result to a variable. After that, pass the resulting string to the text_from_morse_code() function and get the final result.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/738894\/\"> https:\/\/habr.com\/ru\/articles\/738894\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Explaining main algorithm<\/p>\n<p>For a while I\u2019ve been thinking of writing a scientific article. I wanted it to have certain utility. <\/p>\n<p>Morse code is binary: it takes only two values \u2013 either dot (short) or hyphen (long). I figured out that short (s) can stand for two-eye blinking whilst long (l) can indicate left-eye blinking. Another question emerged: how to understand when does one-symbol recording stop? <\/p>\n<p>Empty space between two symbols can be presented by right-eye blinking \u2013 r. If I input singly symbol of short (dot) and long (hyphen), I will blink my right eye once to indicate the space between two symbols. <\/p>\n<p>To separate independent words, one has to blink her right eye twice and get rr. <\/p>\n<p>Hence, I have collected an ordered set of symbols \u2013 r, l, s, &#8212; that can be converted into a full-fledged text. Once I accomplish the transformation, I get an answer. <\/p>\n<h2>Deciphering Python Code<\/h2>\n<p>Let\u2019s take a closer look to the code functions I used.<\/p>\n<p><strong>eye_aspect_ratio(eye)<\/strong>. I use dlib library for face detection. Next, out of 68 facial parameters (these are dots that are spread across human face in a sharpened shape of face), I pick 6 that are responsible for eyes location. In Function I determine whether an eye is opened or closed by counting two Euclidean distances between the upper and lower eyelids of the eye, equally offset from the center (parameters A and B), and Euclidean distance between the right and left corners of the eye (parameter C). The bigger the number, the more open the eye is. <\/p>\n<pre><code class=\"python\">def eye_aspect_ratio(eye): \u00a0\u00a0\u00a0 A = dist.euclidean(eye[1], eye[5]) \u00a0\u00a0\u00a0 B = dist.euclidean(eye[2], eye[4]) \u00a0\u00a0\u00a0 C = dist.euclidean(eye[0], eye[3])  \u00a0\u00a0\u00a0 ear = (A + B) \/ (2.0 * C) \u00a0\u00a0\u00a0 return ear<\/code><\/pre>\n<p><strong>build_plots(name, value, plot) <\/strong>is responsible for output of an image from camera to computer screen. <\/p>\n<pre><code class=\"python\">def build_plots(name, value, plot): \u00a0\u00a0\u00a0 plot = plot.update(value) \u00a0\u00a0\u00a0 cv2.imshow(name, plot)<\/code><\/pre>\n<p>\u00a0<strong>draw_outline(frame, eye) <\/strong>takes camera frame and eyes coordinates. It then fixates eyes with neon-green rings. <\/p>\n<pre><code class=\"python\">def draw_outline(frame, eye): \u00a0\u00a0\u00a0 eyeHull = cv2.convexHull(eye) \u00a0\u00a0\u00a0 cv2.drawContours(frame, [eyeHull], -1, (0, 255, 0), 1)<\/code><\/pre>\n<p><strong>get_args() <\/strong>gets me prerequisite arguments (shape-predictor) for future execution. <\/p>\n<pre><code class=\"python\">def get_args(): \u00a0\u00a0\u00a0 ap = argparse.ArgumentParser() \u00a0\u00a0\u00a0 ap.add_argument(\"-p\", \"--shape-predictor\", required=True, help=\"path to facial landmark predictor\") \u00a0\u00a0\u00a0 args = vars(ap.parse_args()) \u00a0\u00a0\u00a0 return args<\/code><\/pre>\n<p><strong>open_video() <\/strong>opens my front camera and returns prerequisite arguments. <\/p>\n<pre><code class=\"python\">def open_video(): \u00a0\u00a0\u00a0 args = get_args() \u00a0\u00a0\u00a0 print(\"[INFO] loading facial landmark predictor\")  \u00a0\u00a0\u00a0 detector = dlib.get_frontal_face_detector() \u00a0\u00a0\u00a0 predictor = dlib.shape_predictor(args[\"shape_predictor\"])  \u00a0\u00a0\u00a0 print(\"[INFO] starting video stream thread...\") \u00a0\u00a0\u00a0 vs = VideoStream(0).start() \u00a0\u00a0\u00a0 time.sleep(1)  \u00a0\u00a0\u00a0 return vs, detector, predictor<\/code><\/pre>\n<p><strong>calibration(name, flag). <\/strong>Every person\u2019s average <em>ear<\/em> (eye aspect ratio) is different, I can\u2019t use any universal number for this parameter as former simply doesn\u2019t exist. Thus, the calibration comes at handy. It helps to get numbers for several conditions: both eyes opened, both eyes closed, left eye closed &amp; right eye opened and vice versa. After the first beep video opens and I have to hold my eyes opened till next beep. Afterwards, I close my eyes patiently waiting for last beep. Out of all sampling for eyes opened I return a minimum average value <em>ear<\/em> for left and right eyes. Out of all sampling for eyes closed I return a maximum average value <em>ear<\/em> for left and right eyes. Function<strong> <\/strong>thus returns optimal values for correct execution. <\/p>\n<pre><code class=\"python\">def calibration(name, flag): \u00a0\u00a0\u00a0 plotLeft = LivePlot(640, 360, [5, 35], invert=True) \u00a0\u00a0\u00a0 plotRight = LivePlot(640, 360, [5, 35], invert=True)  \u00a0\u00a0\u00a0 (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS[\"left_eye\"] \u00a0\u00a0\u00a0 (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS[\"right_eye\"]  \u00a0\u00a0\u00a0 vs, detector, predictor = open_video() \u00a0\u00a0\u00a0 print('\\a') \u00a0\u00a0\u00a0 time.sleep(3) \u00a0\u00a0\u00a0 time_start = time.time() \u00a0\u00a0\u00a0 both_eyes_open = [] \u00a0\u00a0\u00a0 both_eyes_close = []  \u00a0\u00a0\u00a0 while True: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 frame = vs.read() \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 frame = imutils.resize(frame, width=450) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rects = detector(gray, 0) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for rect in rects: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 shape = predictor(gray, rect) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 shape = face_utils.shape_to_np(shape)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 leftEye = shape[lStart:lEnd] \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rightEye = shape[rStart:rEnd] \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 leftEAR = int(100 * eye_aspect_ratio(leftEye)) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rightEAR = int(100 * eye_aspect_ratio(rightEye))  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if flag == 0: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 earAvg = (leftEAR + rightEAR) \/ 2.0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 both_eyes_open.append(earAvg) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 elif flag == 1: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 earAvg = (leftEAR + rightEAR) \/ 2.0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 both_eyes_close.append(earAvg)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 draw_outline(frame, leftEye) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 draw_outline(frame, rightEye)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 build_plots(\"ImagePlotLeft\", leftEAR, plotLeft) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 build_plots(\"ImagePlotRight\", rightEAR, plotRight) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cv2.imshow(name, frame)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 time_dif = time.time() - time_start \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if cv2.waitKey(25) == ord(\"q\"): \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if time_dif > 5: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 print('\\a') \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break  \u00a0\u00a0\u00a0 cv2.destroyAllWindows() \u00a0\u00a0\u00a0 vs.stop()  \u00a0\u00a0\u00a0 if flag == 0: return min(both_eyes_open) \u00a0\u00a0\u00a0 if flag == 1: return max(both_eyes_close)<\/code><\/pre>\n<p><strong>morse_code_from_eyes(). <\/strong>This is the major function of my code. In real time it monitors and analyzes human eyes. Calibration goes first and is followed by a beep sound, after which the recording starts. Recording goes the similar way as during the calibration, however, now I am comparing the results I get from camera with the ones I got from calibration one step ago. I use counter to trace one symbol per once. If I haven\u2019t utilized counter, I would have stored a large number of symbols per one blink as there wouldn\u2019t be any break. After all symbols are passed, I press the \u201cq\u201d button on a keyboard to finish recording and close front camera.<strong> <\/strong>Then function returns the result of symbols recording. <\/p>\n<pre><code class=\"python\">def morse_code_from_eyes(): \u00a0\u00a0\u00a0 both_open = calibration(\"both_eyes_open\", 0) \u00a0\u00a0\u00a0 both_close = calibration(\"both_eyes_close\", 1)  \u00a0\u00a0\u00a0 plotLeft = LivePlot(640, 360, [5, 35], invert=True) \u00a0\u00a0\u00a0 plotRight = LivePlot(640, 360, [5, 35], invert=True)  \u00a0\u00a0\u00a0 (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS[\"left_eye\"] \u00a0\u00a0\u00a0 (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS[\"right_eye\"]  \u00a0\u00a0\u00a0 vs, detector, predictor = open_video() \u00a0\u00a0\u00a0 print('\\a') \u00a0\u00a0\u00a0 time.sleep(3)  \u00a0\u00a0\u00a0 counter = 0 \u00a0\u00a0\u00a0 points = \"\"  \u00a0\u00a0\u00a0 while True: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 frame = vs.read() \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 frame = imutils.resize(frame, width=450) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rects = detector(gray, 0)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for rect in rects: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 shape = predictor(gray, rect) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 shape = face_utils.shape_to_np(shape)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 leftEye = shape[lStart:lEnd] \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rightEye = shape[rStart:rEnd] \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 leftEAR = int(100 * eye_aspect_ratio(leftEye)) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rightEAR = int(100 * eye_aspect_ratio(rightEye))  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 earAvg = (leftEAR + rightEAR) \/ 2.0  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 draw_outline(frame, leftEye) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 draw_outline(frame, rightEye)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 build_plots(\"ImagePlotLeft\", leftEAR, plotLeft) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 build_plots(\"ImagePlotRight\", rightEAR, plotRight)  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if counter == 0: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if earAvg &lt;= both_close + 1: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 points += \"s\" \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter += 1 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 print(earAvg, \"ssssssssssssssssssss\") \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0elif leftEAR - rightEAR >= 0 and earAvg &lt;= both_open - 3:  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 points += \"p\" \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter += 1 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 print(leftEAR - rightEAR, earAvg, \"pppppppppppppppppp\u0440\u0440\") \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 elif rightEAR - leftEAR >= 0 and earAvg &lt;= both_open - 3: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 points += \"l\" \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter += 1 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 print(rightEAR - leftEAR, earAvg, \"llllllllllllllllllll\") \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if counter == 5: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter = 0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else: \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 counter += 1  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cv2.imshow(\"Frame\", frame) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if cv2.waitKey(25) == ord(\"q\"): \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break  \u00a0\u00a0\u00a0 cv2.destroyAllWindows() \u00a0\u00a0\u00a0 vs.stop() \u00a0\u00a0\u00a0 return points<\/code><\/pre>\n<p><strong>text_from_morse_code(points) <\/strong>is responsible for converting a received stroke of symbols (r,l,s) into a comprehensible text. Firstly, I save a dictionary which keys are a designation in Morse code, and the value is a letter. I split the stroke into \u201cpp\u201d to get separate independent words. Consequently, I go through all the symbols before \u201cp\u201d and convert each one into a letter. After, letters are combined into words and words into sentences. The ultimate result is a returned word. <\/p>\n<pre><code class=\"python\">def text_from_morse_code(points): \u00a0\u00a0\u00a0 alphabet = {\"sl\": \"A\", \"lsss\": \"B\", \"lsls\": \"C\", \"lss\": \"D\", \"s\": \"E\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"ssls\": \"F\", \"lls\": \"G\", \"ssss\": \"H\", \"ss\": \"I\", \"slll\": \"J\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"lsl\": \"K\", \"slss\": \"L\", \"ll\": \"M\", \"ls\": \"N\", \"lll\": \"O\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"slls\": \"P\", \"llsl\": \"Q\", \"sls\": \"R\", \"sss\": \"S\", \"l\": \"T\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"ssl\": \"U\", \"sssl\": \"V\", \"sll\": \"W\", \"lssl\": \"X\", \"lsll\": \"Y\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"llss\": \"Z\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"sllll\": \"1\", \"sslll\": \"2\", \"sssll\": \"3\", \"ssssl\": \"4\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"sssss\": \"5\", \"lssss\": \"6\", \"llsss\": \"7\", \"lllss\": \"8\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"lllls\": \"9\", \"lllll\": \"0\"}      points = points.split(\"pp\")     answer = \"\"     for word in points:         letters = word.split(\"p\")         new_word = \"\"         for letter in letters:             if letter in alphabet:                 new_word += alphabet[letter]             else:                 new_word += \"-\"         answer += new_word + \" \"     return answer<\/code><\/pre>\n<h2>Conclusion<\/h2>\n<p>First you need to run the morse_code_from_eyes() function, and save the result to a variable. After that, pass the resulting string to the text_from_morse_code() function and get the final result.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/738894\/\"> https:\/\/habr.com\/ru\/articles\/738894\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-398134","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/398134","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=398134"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/398134\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=398134"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=398134"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=398134"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}