refact: Dependency Inversion #1
7
src/Interfaces/IObjectDetector.ts
Normal file
7
src/Interfaces/IObjectDetector.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import PredictedObject from '../Models/PredictedObject'
|
||||||
|
|
||||||
|
interface IObjectDetector {
|
||||||
|
getPredictionsFromImageData(videoImage: ImageData): Promise<PredictedObject[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IObjectDetector
|
8
src/Interfaces/IObjectLocator.ts
Normal file
8
src/Interfaces/IObjectLocator.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import IOffset from "./IOffset"
|
||||||
|
import IPredictedObject from "./IPredictedObject"
|
||||||
|
|
||||||
|
interface IObjectLocator {
|
||||||
|
getOffsetsFromPredictions(predictedObject: IPredictedObject): IOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IObjectLocator
|
7
src/Interfaces/IOffset.ts
Normal file
7
src/Interfaces/IOffset.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
interface IOffset {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
hypotenuse: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IOffset
|
8
src/Interfaces/IUiRenderer.ts
Normal file
8
src/Interfaces/IUiRenderer.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import IOffset from "./IOffset"
|
||||||
|
import IPredictedObject from "./IPredictedObject"
|
||||||
|
|
||||||
|
interface IUiRenderer {
|
||||||
|
render(props: { imageData: ImageData, predictedObjects: IPredictedObject[], offsets: IOffset[] }): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IUiRenderer
|
5
src/Interfaces/IVideoCapturer.ts
Normal file
5
src/Interfaces/IVideoCapturer.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
interface IVideoCapturer {
|
||||||
|
imageData: ImageData | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IVideoCapturer
|
14
src/UseCases/Factories/makeObjectDetector.ts
Normal file
14
src/UseCases/Factories/makeObjectDetector.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { DetectedObject } from "@tensorflow-models/coco-ssd"
|
||||||
|
import ObjectDetector from "../ObjectDetector"
|
||||||
|
|
||||||
|
const defaultPredictions = [
|
||||||
|
(prediction: DetectedObject) => prediction.score > 0.6,
|
||||||
|
(prediction: DetectedObject) => prediction.class === 'cat',
|
||||||
|
]
|
||||||
|
|
||||||
|
function makeObjectDetector (filterPredicates?: Function[]): ObjectDetector {
|
||||||
|
if (!filterPredicates) filterPredicates = defaultPredictions
|
||||||
|
return new ObjectDetector({ filterPredicates })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makeObjectDetector
|
14
src/UseCases/Factories/makeObjectLocator.ts
Normal file
14
src/UseCases/Factories/makeObjectLocator.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import IVideo from "../../Interfaces/IVideo"
|
||||||
|
import ObjectLocator from "../ObjectLocator"
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
width: 640,
|
||||||
|
height: 480
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeObjectLocator (props?: IVideo): ObjectLocator {
|
||||||
|
const videoProps = props || defaultProps
|
||||||
|
return new ObjectLocator(videoProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makeObjectLocator
|
15
src/UseCases/Factories/makePredictedObject.ts
Normal file
15
src/UseCases/Factories/makePredictedObject.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { DetectedObject } from "@tensorflow-models/coco-ssd"
|
||||||
|
import IPredictedObject from "../../Interfaces/IPredictedObject"
|
||||||
|
import PredictedObject from "../../Models/PredictedObject"
|
||||||
|
|
||||||
|
function makePredictedObject (p: IPredictedObject) {
|
||||||
|
return new PredictedObject({
|
||||||
|
xOrigin: p.xOrigin,
|
||||||
|
yOrigin: p.yOrigin,
|
||||||
|
width: p.width,
|
||||||
|
height: p.height,
|
||||||
|
class: p.class
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makePredictedObject
|
7
src/UseCases/Factories/makeUiRenderer.ts
Normal file
7
src/UseCases/Factories/makeUiRenderer.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import UiRenderer from "../UiRenderer";
|
||||||
|
|
||||||
|
function makeUiRenderer (): UiRenderer {
|
||||||
|
return new UiRenderer()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makeUiRenderer
|
14
src/UseCases/Factories/makeVideoCatpurer.ts
Normal file
14
src/UseCases/Factories/makeVideoCatpurer.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import IVideo from "../../Interfaces/IVideo"
|
||||||
|
import VideoCapturer from "../VideoCapturer"
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
width: 640,
|
||||||
|
height: 480
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeVideoCapturer (props?: IVideo): VideoCapturer {
|
||||||
|
const videoProps = props || defaultProps
|
||||||
|
return new VideoCapturer(videoProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makeVideoCapturer
|
@ -1,24 +1,20 @@
|
|||||||
import * as tf from '@tensorflow/tfjs'
|
import * as tf from '@tensorflow/tfjs'
|
||||||
import * as cocossd from '@tensorflow-models/coco-ssd'
|
import * as cocossd from '@tensorflow-models/coco-ssd'
|
||||||
import PredictedObject from '../Models/PredictedObject'
|
import IObjectDetector from '../Interfaces/IObjectDetector'
|
||||||
|
import IPredictedObject from '../Interfaces/IPredictedObject'
|
||||||
|
import makePredictedObject from './Factories/makePredictedObject'
|
||||||
|
|
||||||
let instance: ObjectDetector | null = null
|
class ObjectDetector implements IObjectDetector {
|
||||||
|
|
||||||
class ObjectDetector {
|
|
||||||
private mlModel: cocossd.ObjectDetection | null = null
|
private mlModel: cocossd.ObjectDetection | null = null
|
||||||
private filterPredicates: Function[] = []
|
private filterPredicates: Function[] = []
|
||||||
|
|
||||||
constructor (props?: { filterPredicates?: Function[] }) {
|
constructor (props?: { filterPredicates?: Function[] }) {
|
||||||
if (!instance) instance = this
|
|
||||||
|
|
||||||
if (props?.filterPredicates) this.filterPredicates = props.filterPredicates
|
if (props?.filterPredicates) this.filterPredicates = props.filterPredicates
|
||||||
tf.getBackend()
|
tf.getBackend()
|
||||||
|
|
||||||
return instance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertDetectedToPredictedObjects = (detectedObjects: cocossd.DetectedObject[]) => {
|
private convertDetectedToPredictedObjects (detectedObjects: cocossd.DetectedObject[]) {
|
||||||
const predictedObjects: PredictedObject[] = detectedObjects.map(p => new PredictedObject({
|
const predictedObjects: IPredictedObject[] = detectedObjects.map(p => makePredictedObject({
|
||||||
xOrigin: p.bbox[0],
|
xOrigin: p.bbox[0],
|
||||||
yOrigin: p.bbox[1],
|
yOrigin: p.bbox[1],
|
||||||
width: p.bbox[2],
|
width: p.bbox[2],
|
||||||
@ -39,7 +35,7 @@ class ObjectDetector {
|
|||||||
else return true
|
else return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public predictImageStream = async (videoImage: ImageData) => {
|
public async getPredictionsFromImageData (videoImage: ImageData): Promise<IPredictedObject[]> {
|
||||||
const mlModel = await this.loadMlModel()
|
const mlModel = await this.loadMlModel()
|
||||||
const detectedObjects = await mlModel.detect(videoImage)
|
const detectedObjects = await mlModel.detect(videoImage)
|
||||||
const filteredDetections = detectedObjects.filter(p => this.doesDetectionPassFilterPredicates(p))
|
const filteredDetections = detectedObjects.filter(p => this.doesDetectionPassFilterPredicates(p))
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import PredictedObject from "../Models/PredictedObject"
|
import IObjectLocator from "../Interfaces/IObjectLocator"
|
||||||
import Video from "../Models/Video"
|
import IOffset from "../Interfaces/IOffset"
|
||||||
|
import IPredictedObject from "../Interfaces/IPredictedObject"
|
||||||
|
import IVideo from "../Interfaces/IVideo"
|
||||||
|
|
||||||
interface Offset {
|
class ObjectLocator implements IObjectLocator {
|
||||||
x: number,
|
private videoWidth: number
|
||||||
y: number,
|
private videoHeight: number
|
||||||
hypotenuse: number
|
|
||||||
}
|
|
||||||
|
|
||||||
class ObjectLocator {
|
constructor (props: IVideo) {
|
||||||
private video: Video
|
this.videoWidth = props.width
|
||||||
constructor (video: Video) {
|
this.videoHeight = props.height
|
||||||
this.video = video
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detectPredictedObjectLocationFromVideo = (predictedObject: PredictedObject): Offset => {
|
getOffsetsFromPredictions = (predictedObject: IPredictedObject): IOffset => {
|
||||||
const videoCenter = { x: this.video.width / 2, y: this.video.height / 2 }
|
const videoCenter = { x: this.videoWidth / 2, y: this.videoHeight / 2 }
|
||||||
const objectCenter = {
|
const objectCenter = {
|
||||||
x: predictedObject.xOrigin + (predictedObject.width / 2),
|
x: predictedObject.xOrigin + (predictedObject.width / 2),
|
||||||
y: predictedObject.yOrigin + (predictedObject.height / 2)
|
y: predictedObject.yOrigin + (predictedObject.height / 2)
|
||||||
|
44
src/UseCases/UiRenderer.ts
Normal file
44
src/UseCases/UiRenderer.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import IOffset from "../Interfaces/IOffset"
|
||||||
|
import IPredictedObject from "../Interfaces/IPredictedObject"
|
||||||
|
import IUiRenderer from "../Interfaces/IUiRenderer"
|
||||||
|
|
||||||
|
class UiRenderer implements IUiRenderer {
|
||||||
|
render (props: { imageData: ImageData, predictedObjects: IPredictedObject[], offsets: IOffset[] }) {
|
||||||
|
|
||||||
|
const body: HTMLBodyElement = document.querySelector('body')!
|
||||||
|
|
||||||
|
let canvasElement: HTMLCanvasElement = document.querySelector('#videoOutput') as HTMLCanvasElement
|
||||||
|
if (!canvasElement) {
|
||||||
|
canvasElement = document.createElement('canvas')
|
||||||
|
canvasElement.id = 'videoOutput'
|
||||||
|
canvasElement.width = props.imageData.width
|
||||||
|
canvasElement.height = props.imageData.height
|
||||||
|
body.append(canvasElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvasContext = canvasElement.getContext('2d')!
|
||||||
|
canvasContext.clearRect(0, 0, canvasElement.width, canvasElement.height)
|
||||||
|
canvasContext.putImageData(props.imageData, 0, 0)
|
||||||
|
|
||||||
|
props.predictedObjects.forEach(obj => {
|
||||||
|
canvasContext.strokeStyle = 'rgb(0, 255, 0)'
|
||||||
|
canvasContext.strokeRect(obj.xOrigin, obj.yOrigin, obj.width, obj.height)
|
||||||
|
})
|
||||||
|
|
||||||
|
const startPoint = {
|
||||||
|
x: props.imageData.width / 2,
|
||||||
|
y: props.imageData.height / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
props.offsets.forEach(offset => {
|
||||||
|
canvasContext.strokeStyle = 'rgb(255, 0, 0)'
|
||||||
|
canvasContext.beginPath()
|
||||||
|
canvasContext.moveTo(startPoint.x, startPoint.y)
|
||||||
|
canvasContext.lineTo(startPoint.x - offset.x, startPoint.y - offset.y)
|
||||||
|
canvasContext.closePath()
|
||||||
|
canvasContext.stroke()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UiRenderer
|
49
src/UseCases/VideoCapturer.ts
Normal file
49
src/UseCases/VideoCapturer.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import IVideo from '../Interfaces/IVideo'
|
||||||
|
import IVideoCapturer from '../Interfaces/IVideoCapturer'
|
||||||
|
|
||||||
|
class VideoCapturer implements IVideoCapturer {
|
||||||
|
private videoWidth: number
|
||||||
|
private videoHeight: number
|
||||||
|
private videoStream: MediaStream | null = null
|
||||||
|
|
||||||
|
constructor (props: IVideo) {
|
||||||
|
this.videoWidth = props.width
|
||||||
|
this.videoHeight = props.height
|
||||||
|
this.enableCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
private enableCamera = async () => {
|
||||||
|
const webCameraStream = await navigator.mediaDevices.getUserMedia({ video: true })
|
||||||
|
this.videoStream = webCameraStream
|
||||||
|
}
|
||||||
|
|
||||||
|
get imageData () {
|
||||||
|
if (!this.videoStream) return null
|
||||||
|
|
||||||
|
let videoElement: HTMLVideoElement = document.querySelector('#videoView') as HTMLVideoElement
|
||||||
|
if (!videoElement) {
|
||||||
|
videoElement = document.createElement('video')
|
||||||
|
videoElement.width = this.videoWidth
|
||||||
|
videoElement.height = this.videoHeight
|
||||||
|
videoElement.autoplay = true
|
||||||
|
videoElement.srcObject = this.videoStream
|
||||||
|
videoElement.id = 'videoView'
|
||||||
|
videoElement.style.display = 'none'
|
||||||
|
|
||||||
|
const body = document.querySelector('body')!
|
||||||
|
body.appendChild(videoElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvasElement: HTMLCanvasElement = document.createElement('canvas')
|
||||||
|
canvasElement.width = this.videoWidth
|
||||||
|
canvasElement.height = this.videoHeight
|
||||||
|
|
||||||
|
const canvasContext = canvasElement.getContext('2d')!
|
||||||
|
canvasContext.drawImage(videoElement, 0, 0, this.videoWidth, this.videoHeight)
|
||||||
|
|
||||||
|
return canvasContext.getImageData(0, 0, this.videoWidth, this.videoHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoCapturer
|
49
src/app.ts
49
src/app.ts
@ -1,43 +1,42 @@
|
|||||||
import { DetectedObject } from "@tensorflow-models/coco-ssd"
|
import IObjectDetector from './Interfaces/IObjectDetector'
|
||||||
import PredictedObjectCollectionController from "./Controllers/PredictedObjectCollectionController"
|
import IObjectLocator from './Interfaces/IObjectLocator'
|
||||||
import VideoController from './Controllers/VideoController'
|
import IOffset from './Interfaces/IOffset'
|
||||||
import ObjectDetector from './UseCases/ObjectDetector'
|
import IUiRenderer from './Interfaces/IUiRenderer'
|
||||||
import ObjectLocator from "./UseCases/ObjectLocator"
|
import IVideoCapturer from "./Interfaces/IVideoCapturer"
|
||||||
|
|
||||||
const defaultPredictions = [
|
import makeObjectDetector from './UseCases/Factories/makeObjectDetector'
|
||||||
(prediction: DetectedObject) => prediction.score > 0.6,
|
import makeObjectLocator from './UseCases/Factories/makeObjectLocator'
|
||||||
(prediction: DetectedObject) => prediction.class === 'person', // TODO: change to cat
|
import makeUiRenderer from './UseCases/Factories/makeUiRenderer'
|
||||||
]
|
import makeVideoCapturer from './UseCases/Factories/makeVideoCatpurer'
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
private predictedObjectCollectionController: PredictedObjectCollectionController
|
private objectDetector: IObjectDetector
|
||||||
private videoController: VideoController
|
private objectLocator: IObjectLocator
|
||||||
private objectDetector: ObjectDetector
|
private videoCapturer: IVideoCapturer
|
||||||
private objectLocator: ObjectLocator
|
private uiRenderer: IUiRenderer
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.objectDetector = new ObjectDetector({ filterPredicates: defaultPredictions })
|
this.videoCapturer = makeVideoCapturer()
|
||||||
this.predictedObjectCollectionController = new PredictedObjectCollectionController()
|
this.objectDetector = makeObjectDetector()
|
||||||
this.videoController = new VideoController({ width: 640, height: 480 })
|
this.objectLocator = makeObjectLocator()
|
||||||
this.objectLocator = new ObjectLocator(this.videoController.model)
|
this.uiRenderer = makeUiRenderer()
|
||||||
|
|
||||||
|
const eventTarget = new EventTarget()
|
||||||
|
eventTarget.addEventListener('onMediaStreamReady', this.predictImage)
|
||||||
this.predictImage()
|
this.predictImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
predictImage = async () => {
|
predictImage = async () => {
|
||||||
const imageData = this.videoController.imageData
|
const imageData = this.videoCapturer.imageData
|
||||||
|
|
||||||
if (!imageData) {
|
if (!imageData) {
|
||||||
window.requestAnimationFrame(this.predictImage)
|
window.requestAnimationFrame(this.predictImage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const predictedObjects = await this.objectDetector.predictImageStream(imageData)
|
const predictedObjects = await this.objectDetector.getPredictionsFromImageData(imageData)
|
||||||
this.predictedObjectCollectionController.predictedObjects = predictedObjects
|
const offsets: IOffset[] = predictedObjects.map(obj => this.objectLocator.getOffsetsFromPredictions(obj))
|
||||||
const offsets = predictedObjects.map(obj => {
|
this.uiRenderer.render({ imageData, predictedObjects, offsets })
|
||||||
return this.objectLocator.detectPredictedObjectLocationFromVideo(obj)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(offsets)
|
|
||||||
|
|
||||||
window.requestAnimationFrame(this.predictImage)
|
window.requestAnimationFrame(this.predictImage)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user