mirror of
https://github.com/jehiah/TrafficSpeed.git
synced 2025-10-06 08:46:57 +08:00
396 lines
14 KiB
Go
396 lines
14 KiB
Go
package project
|
|
|
|
import (
|
|
"html/template"
|
|
)
|
|
|
|
var Template *template.Template
|
|
|
|
func init() {
|
|
Template = template.Must(template.New("webpage").Funcs(template.FuncMap{
|
|
"DataURI": dataImg,
|
|
}).Parse(tpl))
|
|
}
|
|
|
|
const tpl = `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw==" crossorigin="anonymous">
|
|
</head>
|
|
<body>
|
|
<div class="container"><div class="row"><div class="col-xs-12">
|
|
<h1>Open Speed Data Analysis</h1>
|
|
<p class="float-right"><a href="/save">save</a> <a href="/exit">exit</a></p>
|
|
<form method="GET" action=".">
|
|
|
|
{{ if .Err }}
|
|
<div class="alert alert-danger" role="alert">{{.Err}}</div>
|
|
{{ end }}
|
|
|
|
|
|
<h2>Video File</h2>
|
|
<p><code>{{.Filename}}</code>
|
|
Frames: <code>{{.Frames}}</code>
|
|
Duration: <code>{{.Duration}}</code>
|
|
Resolution: <code>{{.VideoResolution}}</code>
|
|
</p>
|
|
|
|
{{ if eq .Step 1}}
|
|
<h2>Step 1: Crop</h2>
|
|
<p>Instructions: Click on the image below to select the upper left and lower right corner of the frame
|
|
to perform speed analysis on.
|
|
After selecting "point1" and "point2" select the "Continue" button.
|
|
</p>
|
|
<div class="form-group">
|
|
<label>Point 1: <input name="point1" id="point1" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Point 2: <input name="point2" id="point2" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<button type="submit" class="btn btn-primary" name="next" value="2">Continue</button>
|
|
</div>
|
|
|
|
<div><img src="/data/base.png" id="getpoint"></div>
|
|
{{ else if gt .Step 1 }}
|
|
<h2>Step 1: Crop</h2>
|
|
<input type="hidden" name="pre_crop" value="{{.PreCrop}}" />
|
|
<div><img src="/data/base.png" class="img-responsive" style="width:50%; height: 50%;"></div>
|
|
<p>Selected Range <code>{{.PreCrop}}</code>
|
|
Cropped Resolution: <code>{{.Response.PreCroppedResolution}}</code></p>
|
|
{{ else }}
|
|
<input type="hidden" name="pre_crop" value="{{.PreCrop}}" />
|
|
{{ end}}
|
|
|
|
|
|
{{ if eq .Step 2 }}
|
|
<h2>Step 2: Rotation Detection</h2>
|
|
<p>Automatic rotation detection works by picking two points that align with the asis of vehicle movement</p>
|
|
<p>Instructions: Click on the image below to select two points on the axis of movement.
|
|
Typically this will be a lane marking in the middle of the street at either end of the visible range.
|
|
After selecting "point1" and "point2" select the "Continue" button.</h2>
|
|
<p>To skip rotation enter 0x0 as both points.</p>
|
|
|
|
<div class="form-group">
|
|
<label>Point 1: <input name="point1" id="point1" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Point 2: <input name="point2" id="point2" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<button type="submit" class="btn btn-primary" name="next" value="3">Continue</button>
|
|
</div>
|
|
|
|
<div><img src="/data/step2_crop.png" id="getpoint"></div>
|
|
{{ else if gt .Step 2 }}
|
|
<h2>Step 2: Rotation</h2>
|
|
<p>Rotation Angle <code>{{.Rotate}} radians</code></p>
|
|
<div><img src="/data/step3_rotate.png" style="width: 25%; height: 25%;"></div>
|
|
<input type="hidden" name="rotate" value="{{.Rotate | printf "%0.5f"}}" />
|
|
{{ else }}
|
|
<input type="hidden" name="rotate" value="{{.Rotate | printf "%0.5f"}}" />
|
|
{{ end }}
|
|
|
|
|
|
{{ if eq .Step 3 }}
|
|
<h2>Step 3: Crop</h2>
|
|
<p>Instructions: Click on the image below to select the upper left and lower right corner of the frame
|
|
to perform speed analysis on.
|
|
After selecting "point1" and "point2" select the "Continue" button.
|
|
</p>
|
|
|
|
<div class="form-group">
|
|
<label>Point 1: <input name="point1" id="point1" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Point 2: <input name="point2" id="point2" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<button type="submit" class="btn btn-primary" name="next" value="4">Continue</button>
|
|
</div>
|
|
|
|
<div><img src="/data/step3_rotate.png" id="getpoint"></div>
|
|
{{ else if gt .Step 3 }}
|
|
<h2>Step 3: Crop</h2>
|
|
<p>Selected Range <code>{{.PostCrop}}</code>
|
|
Cropped Resolution: <code>{{.Response.CroppedResolution}}</code></p>
|
|
<div><img src="{{.Response.Step4Img}}" style="width: 40%; height: 40%;"></div>
|
|
<input type="hidden" name="post_crop" value="{{.PostCrop}}" />
|
|
{{ else }}
|
|
<input type="hidden" name="post_crop" value="{{.PostCrop}}" />
|
|
{{ end }}
|
|
|
|
{{ if eq .Step 4 }}
|
|
<h2>Step 4: Mask Regions</h2>
|
|
<p>Masking allows the detection of vehicles in different lanes to avoid bleeding into each other,
|
|
and eliminates irrelevant parts of the image (like sidewalks or parked cars).
|
|
Depending on the visual perspective the masked rows should be closer to wheel position to account for
|
|
tall vehicles in the lane.
|
|
</p>
|
|
<p>Instructions: Note the X and Y from the image, and enter masks as a row range <kbd>row:row</kbd>
|
|
or a bounding box pair of coordinates <kbd>10x20 20x30</kbd>. To continue without masks enter a mask of <kbd>-</kbd>.</p>
|
|
|
|
<div class="form-group">
|
|
<label>Mask: <input name="mask" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Mask: <input name="mask" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Mask: <input name="mask" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Mask: <input name="mask" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Mask: <input name="mask" type="text" /></label>
|
|
</div>
|
|
|
|
<div>
|
|
<button type="submit" class="btn" name="next" value="4">Check</button>
|
|
<button type="submit" class="btn btn-primary" name="next" value="5">Continue</button>
|
|
</div>
|
|
|
|
<p>Mouse Position: <span id="mouse_position" style="font-weight:bold;size:14pt;"></span> <span id="mouse_click" style="font-weight:bold;size:14pt;"></span></p>
|
|
|
|
<div><img src="{{.Response.Step4Img}}" id="mousemove"></div>
|
|
{{ else if gt .Step 4 }}
|
|
{{ if .Masks }}
|
|
<h2>Step 4: Mask Regions</h2>
|
|
<p>Masked regions: {{range .Masks }}<code>{{.}}</code> {{end}}</p>
|
|
<div><img src="{{.Response.Step4MaskImg}}" style="width: 40%; height: 40%;"></div>
|
|
{{ range .Masks }}
|
|
<input type="hidden" name="mask" value="{{.}}" />
|
|
{{ end }}
|
|
{{ end }}
|
|
{{ else }}
|
|
{{ range .Masks }}
|
|
<input type="hidden" name="mask" value="{{.}}" />
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ if eq .Step 5 }}
|
|
<h2>Step 5: Object Detection</h2>
|
|
<p>The tunables below adjust what is detected as "active" in an image, and what is treated as a vehicle.</p>
|
|
|
|
<div class="form-group">
|
|
<label>Tolerance: <input name="tolerance" id="tolerance" type="text" value="{{.Tolerance}}" /></label>
|
|
<span class="help-block">The required difference from the background. Valid range: <kbd>0</kbd> to <kbd>255</kbd>. </span>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Blur (pixels): <input name="blur" id="blur" type="text" value="{{.Blur}}" /></label>
|
|
<span class="help-block">Bluring helps define features better and make a single blob for better detection.</span>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Contiguous Pixels: <input name="contiguous_pixels" id="contiguous_pixels" type="text" value="{{.ContiguousPixels}}" /></label>
|
|
<span class="help-block">The number of agacent pixels that are checked when detecting contiguous areas. Must be <kbd>>= 1</kbd></span>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Min Mass: <input name="min_mass" id="min_mass" type="text" value="{{.MinMass}}" /></label>
|
|
<span class="help-block">Filters out small areas that are detected in the image (such as pedestrians).</span>
|
|
</div>
|
|
<button type="submit" class="btn" name="next" value="5">Check</button>
|
|
<button type="submit" class="btn btn-primary" name="next" value="6">Continue</button>
|
|
|
|
<p>Background Image:</p>
|
|
<img src="{{.Response.BackgroundImg}}" style="width: 50%; height: 50%;">
|
|
|
|
<div class="row">
|
|
{{ range .Response.FrameAnalysis }}
|
|
<div class="col-xs-12 col-md-8 col-lg-6">
|
|
<h4>Time index <code>{{.Timestamp}} seconds</code></h4>
|
|
|
|
<p>Frame: (before masking)</p>
|
|
<img src="{{.BaseGif}}" class="img-responsive" alt="base-gif">
|
|
|
|
<p>Active Image:</p>
|
|
<img src="{{.HighlightGif}}" class="img-responsive" alt="highlight-gif">
|
|
|
|
<p>Detected Areas: (after masking)</p>
|
|
<img src="{{.ColoredGif}}" class="img-responsive" alt="colored-gif">
|
|
|
|
</div>
|
|
|
|
<div class="col-xs-12 col-md-8 col-lg-6">
|
|
<h4>Time index <code>{{.Timestamp}} seconds</code></h4>
|
|
|
|
<p>Frame: (before masking)</p>
|
|
<img src="{{.Base}}" class="img-responsive" alt="base">
|
|
|
|
<p>Active Image:</p>
|
|
<img src="{{.Highlight}}" class="img-responsive" alt="highlight">
|
|
|
|
<p>Detected Areas: (after masking)</p>
|
|
<img src="{{.Colored}}" class="img-responsive" alt="colored">
|
|
|
|
{{ if .Positions }}
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th></th><th>Mass</th><th>Position</th><th>Size</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{ range $i, $p := .Positions }}
|
|
<tr>
|
|
<th><small>{{$i}}</small></th>
|
|
<td>{{$p.Pixels }} pixels</td>
|
|
<td>{{$p.Center.X}}x{{$p.Center.Y}}</td>
|
|
<td>{{$p.Bounds.Dx}}x{{$p.Bounds.Dy}}</td>
|
|
</tr>
|
|
{{ end }}
|
|
</tbody>
|
|
</table>
|
|
{{ end }}
|
|
</div>
|
|
|
|
{{ end }}
|
|
</div>
|
|
{{ else if gt .Step 5 }}
|
|
<h2>Step 5: Object Detection</h2>
|
|
<p>Tolerance: <code>{{.Tolerance}}</code></p>
|
|
<p>Blur: <code>{{.Blur}}</code></p>
|
|
<p>Contiguous Pixels: <code>{{.ContiguousPixels}}</code></p>
|
|
<p>Min Mass: <code>{{.MinMass}}</code></p>
|
|
|
|
<p>Background Image:</p>
|
|
<img src="{{.Response.BackgroundImg}}" style="width: 50%; height: 50%;">
|
|
|
|
<input type="hidden" name="tolerance" value="{{.Tolerance}}" />
|
|
<input type="hidden" name="blur" value="{{.Blur}}" />
|
|
<input type="hidden" name="contiguous_pixels" value="{{.ContiguousPixels}}" />
|
|
<input type="hidden" name="min_mass" value="{{.MinMass}}" />
|
|
{{ else }}
|
|
<input type="hidden" name="tolerance" value="{{.Tolerance}}" />
|
|
<input type="hidden" name="blur" value="{{.Blur}}" />
|
|
<input type="hidden" name="contiguous_pixels" value="{{.ContiguousPixels}}" />
|
|
<input type="hidden" name="min_mass" value="{{.MinMass}}" />
|
|
{{ end }}
|
|
|
|
{{ if eq .Step 6 }}
|
|
<h2>Step 6: Position Detection</h2>
|
|
|
|
{{ if .Response.VehiclePositions }}
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Frame</th><th>Time</th><th>Vehicle</th><th>Position</th><th>Mass</th><th>Size</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{ range .Response.VehiclePositions }}
|
|
<tr>
|
|
<th>{{.Frame}}</th>
|
|
<td>{{.Time}}</td>
|
|
<td>{{.VehicleID}}</td>
|
|
<td>{{.Position.Center.X}}x{{.Position.Center.Y}}</td>
|
|
<td>{{.Position.Pixels }} pixels</td>
|
|
<td>{{.Position.Bounds.Dx}}x{{.Position.Bounds.Dy}}</td>
|
|
</tr>
|
|
{{ end }}
|
|
</tbody>
|
|
</table>
|
|
{{ end }}
|
|
|
|
{{ end }}
|
|
|
|
{{ if eq .Step 7 }}
|
|
<h2>Step 7: Speed Calibration</h2>
|
|
|
|
<p>Calibrations: {{range .Calibrations }}<code>{{.Pretty}}</code><br/>{{end}}</p>
|
|
|
|
{{ range .Calibrations }}
|
|
<input type="hidden" name="calibration" value="{{.}}" />
|
|
{{ end }}
|
|
|
|
<div class="form-group">
|
|
<label>Seek (seconds): <input name="seek" id="seek" type="text" value="{{.Seek}}" /></label>
|
|
<button type="submit" class="btn btn-primary" name="next" value="6">Seek</button>
|
|
</div>
|
|
|
|
{{ if .Seek }}
|
|
<div class="form-group">
|
|
<label>Point 1: <input name="point1" id="point1" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Point 2: <input name="point2" id="point2" type="text" /></label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Distance (inches): <input name="inches" id="inches" type="text" /></label>
|
|
<span class="help-block">NV200 wheelbase is 115.2" </span>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary" name="next" value="6">Record Calibration</button>
|
|
{{ end }}
|
|
<button type="submit" class="btn btn-primary" name="next" value="7">Done</button>
|
|
|
|
<img src="{{.Response.Step6Img}}" id="getpoint">
|
|
{{ else if gt .Step 7 }}
|
|
{{ range .Calibrations }}
|
|
<input type="hidden" name="calibration" value="{{.}}" />
|
|
{{ end }}
|
|
{{ end }}
|
|
|
|
{{ range .Response.DebugImages }}
|
|
<img src="{{.}}" style="width: 50%; height: 50%;">
|
|
{{ end }}
|
|
|
|
</form>
|
|
</div></div>
|
|
<div class="row"> </div>
|
|
</div>
|
|
<script type="text/javascript">
|
|
function pos(el, event) {
|
|
var pos_x = event.offsetX ? event.offsetX : event.pageX - el.offsetLeft;
|
|
var pos_y = event.offsetY ? event.offsetY : event.pageY - el.offsetTop;
|
|
return pos_x + "x" + pos_y
|
|
}
|
|
|
|
function getpoint(event) {
|
|
var i = document.getElementById("getpoint")
|
|
|
|
var title = "Point 1"
|
|
var t = document.getElementById("point1")
|
|
if (t.value != "") {
|
|
title = "Point 2"
|
|
t = document.getElementById("point2")
|
|
if (t.value != "") {
|
|
t.value = ""
|
|
title = "Point 1"
|
|
t = document.getElementById("point1")
|
|
}
|
|
}
|
|
t.value = pos(i, event)
|
|
alert(title + " is " + t.value)
|
|
}
|
|
function mousemove(event) {
|
|
var i = document.getElementById("mousemove")
|
|
document.getElementById("mouse_position").innerHTML = pos(i, event)
|
|
}
|
|
function mouseclick(event) {
|
|
var i = document.getElementById("mousemove")
|
|
document.getElementById("mouse_click").innerHTML = "last click: " + pos(i, event)
|
|
}
|
|
function on(pattern, event, f) {
|
|
var el = document.getElementById(pattern)
|
|
if (el == null) {
|
|
return
|
|
}
|
|
el.addEventListener(event, f, true)
|
|
}
|
|
|
|
on("getpoint", "click", getpoint)
|
|
on("mousemove", "click", mouseclick)
|
|
on("mousemove", "mousemove", mousemove)
|
|
on("mousemove", "mouseout", function(){
|
|
document.getElementById("mouse_position").innerHTML = ""
|
|
})
|
|
|
|
</script>
|
|
</body>
|
|
</html>`
|