Add (kind of creepy) result visualization
This commit is contained in:
parent
5ae05ca42a
commit
9b7f2f2efb
5 changed files with 87 additions and 11 deletions
3
deps.edn
3
deps.edn
|
|
@ -9,8 +9,7 @@
|
||||||
;; clojurescript build tool and dependencies (see also shadow-cljs.edn)
|
;; clojurescript build tool and dependencies (see also shadow-cljs.edn)
|
||||||
{:cljs {:extra-deps {thheller/shadow-cljs {:mvn/version "2.8.109"}
|
{:cljs {:extra-deps {thheller/shadow-cljs {:mvn/version "2.8.109"}
|
||||||
appliedscience/js-interop {:mvn/version "0.2.5"}
|
appliedscience/js-interop {:mvn/version "0.2.5"}
|
||||||
reagent {:mvn/version "1.0.0-alpha1"}}
|
reagent {:mvn/version "1.0.0-alpha1"}}}
|
||||||
:main-opts ["-m" "shadow.cljs.devtools.cli"]}
|
|
||||||
|
|
||||||
;; packaging the server as a standalone jar for production
|
;; packaging the server as a standalone jar for production
|
||||||
:uberjar {:extra-deps {luchiniatwork/cambada {:mvn/version "1.0.2"}}
|
:uberjar {:extra-deps {luchiniatwork/cambada {:mvn/version "1.0.2"}}
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,18 @@ p:first-of-type {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
p:last-of-type {
|
p:last-of-type {
|
||||||
margin-bottom: 24px
|
margin-bottom: 24px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: hsl(240, 100%, 70%);
|
background: hsl(240, 100%, 70%);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -128,3 +136,27 @@ video#capture {
|
||||||
transform: scale(-1, 1);
|
transform: scale(-1, 1);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#snapshot-preview {
|
||||||
|
z-index: 100;
|
||||||
|
background: #fff;
|
||||||
|
color: hsl(240, 100%, 70%);
|
||||||
|
border: 2px solid hsl(240, 100%, 70%);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 8px 0 hsla(240, 100%, 70%, 20%);
|
||||||
|
width: 480px;
|
||||||
|
max-width: 88%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#snapshot-preview img {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto 32px;
|
||||||
|
transform: scale(-1, 1);
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#snapshot-preview button {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
[reagent.dom :as dom]
|
[reagent.dom :as dom]
|
||||||
[heyarne.all-my-friends.views :as views]))
|
[heyarne.all-my-friends.views :as views]))
|
||||||
|
|
||||||
|
|
||||||
(defn draw-results [ctx _video predictions]
|
(defn draw-results [ctx _video predictions]
|
||||||
;; remove previous results
|
;; remove previous results
|
||||||
(.clearRect ctx 0 0 (.. ctx -canvas -width) (.. ctx -canvas -height))
|
(.clearRect ctx 0 0 (.. ctx -canvas -width) (.. ctx -canvas -height))
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,8 @@
|
||||||
canvas (.querySelector container "#result")
|
canvas (.querySelector container "#result")
|
||||||
ctx (.getContext canvas "2d")
|
ctx (.getContext canvas "2d")
|
||||||
model (promisify init-model)
|
model (promisify init-model)
|
||||||
stream (promisify init-webcam)]
|
stream (promisify init-webcam)
|
||||||
|
halt? (:halt? (r/props this))]
|
||||||
(-> (js/Promise.all #js [model stream])
|
(-> (js/Promise.all #js [model stream])
|
||||||
(.then (fn [[model stream]]
|
(.then (fn [[model stream]]
|
||||||
(println "model and stream initialized")
|
(println "model and stream initialized")
|
||||||
|
|
@ -91,12 +92,13 @@
|
||||||
(set! (.-srcObject video) stream)
|
(set! (.-srcObject video) stream)
|
||||||
(set! (.-onloadedmetadata video)
|
(set! (.-onloadedmetadata video)
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(.play video)
|
|
||||||
(set! (.-width canvas) (.-videoWidth video))
|
(set! (.-width canvas) (.-videoWidth video))
|
||||||
(set! (.-height canvas) (.-videoHeight video))
|
(set! (.-height canvas) (.-videoHeight video))
|
||||||
|
(when-not halt?
|
||||||
|
(.play video)
|
||||||
;; detect-faces will continously be called via requestAnimationFrame
|
;; detect-faces will continously be called via requestAnimationFrame
|
||||||
;; on-faces-detected receives the canvas context as first param and detected predictions as second
|
;; on-faces-detected receives the canvas context as first param and detected predictions as second
|
||||||
(detect-faces model video (partial (:on-faces-detected (r/props this)) ctx))))
|
(detect-faces model video (partial (:on-faces-detected (r/props this)) ctx)))))
|
||||||
;; make the initialized variables available for
|
;; make the initialized variables available for
|
||||||
;; component-did-update
|
;; component-did-update
|
||||||
(swap! state assoc
|
(swap! state assoc
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
(ns heyarne.all-my-friends.views
|
(ns heyarne.all-my-friends.views
|
||||||
(:require [reagent.core :as r]
|
(:require [reagent.core :as r]
|
||||||
|
[applied-science.js-interop :as j]
|
||||||
[heyarne.all-my-friends.facemesh :refer [webcam-facemesh]]))
|
[heyarne.all-my-friends.facemesh :refer [webcam-facemesh]]))
|
||||||
|
|
||||||
;; minimalistic re-frame-like event handling
|
;; minimalistic re-frame-like event handling
|
||||||
|
|
@ -10,7 +11,9 @@
|
||||||
{:welcome/continue (fn [db _]
|
{:welcome/continue (fn [db _]
|
||||||
(assoc db :status :running))
|
(assoc db :status :running))
|
||||||
:running/snapshot (fn [db result]
|
:running/snapshot (fn [db result]
|
||||||
(assoc-in db [:snapshots :current] result))})
|
(assoc-in db [:snapshots :current] result))
|
||||||
|
:running/discard-snapshot (fn [db _]
|
||||||
|
(update db :snapshots dissoc :current))})
|
||||||
|
|
||||||
(defn dispatch [[event data]]
|
(defn dispatch [[event data]]
|
||||||
(when-let [handler (events event)]
|
(when-let [handler (events event)]
|
||||||
|
|
@ -36,6 +39,45 @@ Seit der globalen Covid19-Pandemie sind wir alle dazu gezwungen, auf physischen
|
||||||
(.drawImage ctx video-elem 0 0 (.-width canvas) (.-height canvas))
|
(.drawImage ctx video-elem 0 0 (.-width canvas) (.-height canvas))
|
||||||
(.getImageData ctx 0 0 (.-width canvas) (.-height canvas))))
|
(.getImageData ctx 0 0 (.-width canvas) (.-height canvas))))
|
||||||
|
|
||||||
|
(defn draw-path [ctx path]
|
||||||
|
(.moveTo ctx (aget path 0 0) (aget path 0 1))
|
||||||
|
(doseq [[x y] (rest path)]
|
||||||
|
(.lineTo ctx x y))
|
||||||
|
(.stroke ctx))
|
||||||
|
|
||||||
|
(defn preview [{{:keys [video predictions]} :snapshot}]
|
||||||
|
(let [canvas (js/document.createElement "canvas")
|
||||||
|
ctx (.getContext canvas "2d")]
|
||||||
|
;; this is most probably not the most beautiful component i have ever written
|
||||||
|
(set! (.-predictions js/window) predictions)
|
||||||
|
(set! (.-width canvas) (.-width video))
|
||||||
|
(set! (.-height canvas) (.-height video))
|
||||||
|
(set! (.-strokeStyle ctx) "pink")
|
||||||
|
(set! (.-lineWidth ctx) 2)
|
||||||
|
(set! (.-lineWidth ctx) "round")
|
||||||
|
#_(.putImageData ctx video 0 0)
|
||||||
|
(doseq [prediction predictions]
|
||||||
|
(draw-path ctx (.slice (j/get-in prediction [:annotations :leftEyebrowLower]) 2))
|
||||||
|
(draw-path ctx (j/get-in prediction [:annotations :leftEyeLower0]))
|
||||||
|
(draw-path ctx (j/get-in prediction [:annotations :leftEyeUpper0]))
|
||||||
|
(draw-path ctx (.slice (j/get-in prediction [:annotations :rightEyebrowLower]) 2))
|
||||||
|
(draw-path ctx (j/get-in prediction [:annotations :rightEyeLower0]))
|
||||||
|
(draw-path ctx (j/get-in prediction [:annotations :rightEyeUpper0]))
|
||||||
|
(draw-path ctx (.slice (j/get-in prediction [:annotations :lipsUpperOuter]) 3 -3))
|
||||||
|
(draw-path ctx (.slice (j/get-in prediction [:annotations :lipsLowerInner]) 3 -3))
|
||||||
|
(draw-path ctx (.slice (j/get-in prediction [:annotations :lipsLowerOuter]) 1 -2))
|
||||||
|
(draw-path ctx (.concat (j/get-in prediction [:annotations :noseTip])
|
||||||
|
(j/get-in prediction [:annotations :midwayBetweenEyes])))
|
||||||
|
(draw-path ctx (.slice (j/get-in prediction [:annotations :silhouette]) 5 -5)))
|
||||||
|
[:img {:src (.toDataURL canvas)}]))
|
||||||
|
|
||||||
|
(defn snapshot [{:keys [current-snapshot]}]
|
||||||
|
(let [compliment (rand-nth ["Hübschi!" "Sweet." "Nice!" ":)" "Uh lala~"])]
|
||||||
|
[:div#snapshot-preview.container
|
||||||
|
[:p compliment]
|
||||||
|
[preview {:snapshot current-snapshot}]
|
||||||
|
[:button {:on-click #(println "submit")} "Abschicken"]
|
||||||
|
[:button {:on-click #(dispatch [:running/discard-snapshot])} "Lieber noch eins"]]))
|
||||||
|
|
||||||
(defn running [{:keys [on-faces-detected halt?]}]
|
(defn running [{:keys [on-faces-detected halt?]}]
|
||||||
(let [result (atom {:video nil
|
(let [result (atom {:video nil
|
||||||
|
|
@ -54,13 +96,15 @@ Seit der globalen Covid19-Pandemie sind wir alle dazu gezwungen, auf physischen
|
||||||
(defn app [{:keys [on-faces-detected]}]
|
(defn app [{:keys [on-faces-detected]}]
|
||||||
(let [state @state
|
(let [state @state
|
||||||
status (:status state)
|
status (:status state)
|
||||||
halt? (some? (get-in state [:snapshots :current]))]
|
current-snapshot (get-in state [:snapshots :current])]
|
||||||
[:<>
|
[:<>
|
||||||
[welcome-message {:hidden? (not= :welcome-message status)}]
|
[welcome-message {:hidden? (not= :welcome-message status)}]
|
||||||
|
(when current-snapshot
|
||||||
|
[snapshot {:current-snapshot current-snapshot}])
|
||||||
(case status
|
(case status
|
||||||
:permission-rejected [:div "Sad :("]
|
:permission-rejected [:div "Sad :("]
|
||||||
:running [running {:on-faces-detected on-faces-detected
|
:running [running {:on-faces-detected on-faces-detected
|
||||||
:halt? halt?}]
|
:halt? (some? current-snapshot)}]
|
||||||
|
|
||||||
;; default
|
;; default
|
||||||
nil)]))
|
nil)]))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue