Jump to content

[Projekts] GPX video pārvalka (overlay) renderēšana


Recommended Posts

AndrisBB

Tākā ārā pamatīgs karstums un neko citu interesantu darīt tāpat nav ko, tad jāieposto šodienas un vakardienas mini prototip-projekts.

 

Problēma:

Esu ievērijis ka ir pamatīgs trūkums softwārei, kas var paņemt video un uzrenderēt pa virsu kautkādus vidžetus ar sirdsdarbību, ātrumu, lokāciju utt no GPX faila. Itkā ir Garmin Virb Edit, bet tas jau pamests ntos gadus, plus softwāre ar tāda jancīga un neko nopietnu tur izdarīt tāpat nevar. Nekādus citus jēdzīgus variantus redzējis neesu.

 

Risinājums:

Uztaisīt Gstreamer pluginu, kas var nolasīt GPX failu un uzrenderēt widžetus pa virsu video. Widžeti varētu būt SVG faili ar kautkādu grafiku un tad uz katra kadra palaižas JavaScript funkcija kura saņem GPX datus, kas atbilst tam laikam, modificē SVG un atgriež pluginam, lai var uzrenderēt SVG pa virsu.

Reāli pielietojums varētu būt ka vienkārši norenderē to video uz kautkāda zaļā fona, ko pēctam var izmantot jebkurā video editēšanas softā, graizīt utt. Bet var arī renderēt pa virsu jebkuram video.

 

Arhitektūra:

Lai renderētu SVG izmantoju RSVG bibliotēku no Glib. 

Kā JavaScript dziēju izmantoju Duktape. Ļoti minimāls dzinējs kas neko nezin par dokumentiem, tīklu utt, var izpildīt tik parastu JavaScript.

Uz katru kadru Gstreamer plugins padod GPX datus, JavaScript nolasa, modificē SVG un atgriež. Tad plugins uzrenderē to SVG kadram pa virsu.

 

Lai ierakstītu piemēram sirdsdarbību uz video un iekodētu iekš h264 var izmantot ļoti pronitīvu pipelainu.

 

gst-launch-1.0 videotestsrc num-buffers=600 \
    ! video/x-raw,width=1280,height=960 \
    ! videoconvert \
    ! gpxoverlay \
        location=../src/gpxoverlay/assets/atom.svg \
        script=../src/gpxoverlay/assets/test.js \
        gpx-location=../src/gpxoverlay/assets/route.gpx \
    ! videoconvert \
    ! queue \
    ! x264enc \
    ! mp4mux \
    ! filesink \
        location=../video.mp4

 

GPX izskatās apmēram tā, kur vienkārši XML datu punkti (koordinātas padzēsu):

<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="Garmin Connect" version="1.1"
  xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/11.xsd"
  xmlns:ns3="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
  xmlns="http://www.topografix.com/GPX/1/1"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
  <metadata>
    <link href="connect.garmin.com">
      <text>Garmin Connect</text>
    </link>
    <time>2022-07-16T14:42:51.000Z</time>
  </metadata>
  <trk>
    <name>South Gloucestershire Cycling</name>
    <type>cycling</type>
    <trkseg>
      <trkpt lat="" lon="">
        <ele>77.59999847412109375</ele>
        <time>2022-07-16T14:44:21.000Z</time>
        <extensions>
          <ns3:TrackPointExtension>
            <ns3:atemp>30.0</ns3:atemp>
            <ns3:hr>103</ns3:hr>
            <ns3:cad>78</ns3:cad>
          </ns3:TrackPointExtension>
        </extensions>
      </trkpt>
      <trkpt lat="" lon="">
        <ele>77.8000030517578125</ele>
        <time>2022-07-16T14:44:22.000Z</time>
        <extensions>
          <ns3:TrackPointExtension>
            <ns3:atemp>30.0</ns3:atemp>
            <ns3:hr>103</ns3:hr>
            <ns3:cad>78</ns3:cad>
          </ns3:TrackPointExtension>
        </extensions>
      </trkpt>
      <trkpt lat="" lon="">
        <ele>78</ele>
        <time>2022-07-16T14:44:24.000Z</time>
        <extensions>
          <ns3:TrackPointExtension>
            <ns3:atemp>30.0</ns3:atemp>
            <ns3:hr>102</ns3:hr>
            <ns3:cad>78</ns3:cad>
          </ns3:TrackPointExtension>
        </extensions>
      </trkpt>
      <trkpt lat="" lon="">
        <ele>78.59999847412109375</ele>
        <time>2022-07-16T14:44:27.000Z</time>
        <extensions>
          <ns3:TrackPointExtension>
            <ns3:atemp>30.0</ns3:atemp>
            <ns3:hr>104</ns3:hr>
            <ns3:cad>78</ns3:cad>
          </ns3:TrackPointExtension>
        </extensions>
      </trkpt>
      <trkpt lat="" lon="">
        <ele>79.1999969482421875</ele>
        <time>2022-07-16T14:44:31.000Z</time>
        <extensions>
          <ns3:TrackPointExtension>
            <ns3:atemp>30.0</ns3:atemp>
            <ns3:hr>105</ns3:hr>
            <ns3:cad>77</ns3:cad>
          </ns3:TrackPointExtension>
        </extensions>
      </trkpt>

 

Loti vienkāršs SVG widžets (jātrod kāds mākslinkies, kas sazīmē ko interesantāku)

 

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="312" height="120" viewBox="0 0 78 30">
    <text xml:id="timestamp_id" x="2" y="29" fill="blue" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#1a00ff;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none">
    </text>
    <rect style="fill:#494949;fill-opacity:0.3;stroke:none;" width="78" height="30"/>
    <text xml:id="label_id" x="20" y="8" fill="blue" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none">
        HEART RATE
    </text>
    <text xml:id="hr_id" x="20" y="20" fill="blue" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#1ad93e;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none">
        140 BPM
    </text>
    <path xml:id="heart_id"
        transform="scale(1.2 1.2)"
        d="m 14,4 a 4.0366464,4.1745268 0.0194517 0 0 -2.745618,-1.114394 4.0366464,4.1745268 0.0194517 0 0 -2.907338,1.281633 l -0.0016,-0.0017 -0.0016,0.0017 a 4.0366464,4.1745268 0.0194517 0 0 -2.907286,-1.28159 4.0366464,4.1745268 0.0194517 0 0 -4.036694,4.174578 4.0366464,4.1745268 0.0194517 0 0 1.239257,3.00659 l -0.0016,0.002 5.707812,5.90276 5.708275,-5.90325 -3.96e-4,-3.8e-4 a 4.0366464,4.1745268 0.0194517 0 0 1.23801,-3.00797 4.0366464,4.1745268 0.0194517 0 0 -1.290981,-3.060131 z"
        fill="#ff00ff"/>
</svg>

 

Un pats JavaScripts

 

var document = new Document("../src/gpxoverlay/assets/atom.svg");
this.Document = document;

var counter = 0;

function start()
{
    print("JS start called");
}

function render(point)
{
    counter = counter + 1;

    if(point != undefined) {
        // print(point.timestamp);
        var el = Document.getElementById("timestamp_id");
        if(el != undefined) {
            el.innerHTML = point.timestamp;
        }

        el = Document.getElementById("hr_id");
        if(el != undefined) {
            el.innerHTML = point.hr.toString() + " BPM";
        }

        el = Document.getElementById("heart_id");
        if(el != undefined) {
            var fill = "#ffffff"
            if(counter >= 0 && counter < 15) {
                fill = "#ff0000";
            }
            
            if(counter > 30) {
                counter = 0;
            }
            el.setAttribute("fill", fill);
        }
    }

    return document.stringify();
}

 

 

Gstreamera pipeline sanāk tā (kautkā baigi garais grafiks):

 

 

 

 

 

 

 

graphviz.png

Rezultātā video (ja forums rādīs):

 

 

  • Atbalstu 1
Link to comment
Share on other sites

AndrisBB
Posted (edited)

Kods te https://github.com/AndrisBB/gstreamer-gpx-overlay

Pagaidām knapi turās kopā, bet strādā.

 

Kas jādara:

* Jāuzraksta labāks parsers priekš GPX failiem. Pagaidām nolasa tikai dažās vērtības, atbalsta tikai vienu segmentu un ielasa vienkārši punktus listē. Renderēšanas laikā iet cauri visai listei kamēr atrod vajadzīgo. Kautko gudrāku tur vajag.

* Duktape JavaScript dzinējs ir diezgan interesants veidojums, ar kuru komunicēt nav tas vieglākais uzdevums. Jāstumda mainīgos uz no/staka. Jādomā kā kautkādā assamblerī. 

Domu var saprast te https://github.com/AndrisBB/gstreamer-gpx-overlay/blob/master/src/gpxoverlay/gstduktape.c

Dokumentācija te. Kā izsaukt funckijas no C uz Javascript, vai no JavasScript uz C. https://duktape.org/index.html

* Duktape ir pliks JavaScript dzinējs, nav tur ne Document, ne Element, nu nekā ko piedāvā browzers. Pat ne Console.log(). Nākas visu rakstīt pašam.

Piemēram pagaidām no 'Document' ir viena funkcija -> getElementByID.

Tāpat arī elementam ir tikai 'innerHTML' propertijs un 'setAttribute'.

Izmantoju GDOMe bibliotēku SVG/HTML parsēšanai un modificēšanai.

* Renderēšana pagaidām diezgan vārga. Izmantoju rSVG bibliotēku, lai uzrenderētu SVG uz kadra. Tas pagaidām knapi turas kopā. Krāsas ar var redzēt ka nav pareizās. RGBA sajaukti vietām, bet slinkums tagad risināt.

* Nav nekādu widžetu. Jāpasūta kādam dizainerim iekš Fiverr.com vai kur līdzīgi, lai sazīmē ko interesantu.

 

Īsumā - var teikt ka kautkas darbojas, bet nu tālu līdz paberigšanai, cerams nepazudīs interese un beigās kas lietderīgs sanāks.

 

 

 

 

Edited by AndrisBB
Link to comment
Share on other sites

AndrisBB

Garmin Virb Edit var uztaisīt ko tādu

 

 

Link to comment
Share on other sites

AndrisBB
Posted (edited)

Šodien mazliet pačakarējos un uztaisiju primitīvu 'elavation' widžetu.

Venkārši sākumā savāc visus punktus un tad uzrenderē pligonu un pašreizējo punktu.

 

SVG

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="240" viewBox="0 0 480 240">
    <rect style="fill:#494949;fill-opacity:0.3;stroke:none;" width="480" height="240"/>
    <text xml:id="label_id" x="8" y="24" fill="blue" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:26px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#00ff00;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none">
        ELEVATION
    </text>
    <polygon xml:id="elevation_graph" points="480,120 480,240 0,240 0,48 48,96 96,72 144,100 192,144 240,100 288,84 336,73 384,65 432,55" style="fill:#6adb5c;fill-opacity:0.9" transform=""></polygon>
    <circle xml:id="current_pos" stroke="#000000" stroke-width="3px" cx="-10"  cy="-10" fill="#ffffff" r="10" transform=""></circle>
</svg>

 

JavaScripts

var document = new Document("../src/gpxoverlay/assets/elevation.svg");
this.Document = document;

var counter = 0;

var _segment = null;

var _elevation_min = Number.MAX_VALUE;
var _elevation_max = Number.MIN_VALUE;

var MAX_POINTS_TO_RENDER = 480;
var GRAPH_WIDTH = 480;
var GRAPH_HEIGHT = 240;

var _points_to_render = 0;

function start(segment)
{
    _segment = segment;

    // Find min and max elevation values
    var trkPoints = segment.trkPoints;
    for (var i = 0; i < trkPoints.length; i++) {
        if(trkPoints[i].elevation > _elevation_max) {
            _elevation_max = trkPoints[i].elevation;
        }
        if(trkPoints[i].elevation < _elevation_min) {
            _elevation_min = trkPoints[i].elevation;
        }
    }

    if(trkPoints.length >= MAX_POINTS_TO_RENDER) {
        _points_to_render = MAX_POINTS_TO_RENDER;
    }
    else {
        _points_to_render = trkPoints.length;
    }
}

function filter_points(points, idx) {
    
    var first_point = 0;

    if(idx < _points_to_render/2) {
        first_point = 0;
    }
    else {
        first_point = idx - _points_to_render/2;
    }

    return points.slice(first_point, first_point + _points_to_render);
}

function render(point)
{
    var points = filter_points(_segment.trkPoints, point.idx);
    
    var polygon = "480,240 0,240";

    for(var i = 0; i < points.length; i++) {
        var x = i * (GRAPH_WIDTH/points.length);
        var y = GRAPH_HEIGHT - parseInt(((_elevation_max - _elevation_min)/GRAPH_HEIGHT) * points[i].elevation);

        polygon = polygon.concat(" " + x + "," + y);

        if(point.idx == points[i].idx) {
            var el_pos = Document.getElementById("current_pos");
            if(el_pos != undefined) {
                el_pos.setAttribute("cx", x);
                el_pos.setAttribute("cy", y);
            }
        }
    }

    var el = Document.getElementById("elevation_graph");
    if(el != undefined) {
        el.setAttribute("points", polygon);
    }

    el = Document.getElementById("label_id");
    if(el != undefined) {
        el.innerHTML = "ELEVATION " + point.elevation.toFixed(2).toString() + "M";
    }

    return document.stringify();
}

 

 

 

Edited by AndrisBB
Link to comment
Share on other sites

AndrisBB

Divi widžeti uz parasta testa fona

gst-launch-1.0 videotestsrc \
    ! video/x-raw,width=1280,height=960 \
    ! videoconvert \
    ! gpxoverlay \
        script-location=../src/gpxoverlay/assets/elevation.js \
        gpx-location=../src/gpxoverlay/assets/activity_9090497773.gpx \
        x=790 \
        y=10 \
    ! gpxoverlay \
        script-location=../src/gpxoverlay/assets/test.js \
        gpx-location=../src/gpxoverlay/assets/activity_9090497773.gpx \
        x=10 \
        y=10 \
    ! videoconvert \
    ! gtksink

 

Pa virsu video failam

gst-launch-1.0 filesrc location=../rec.mp4 \
    ! decodebin name=dec \
    ! videoconvert \
    ! gpxoverlay \
        script-location=../src/gpxoverlay/assets/elevation.js \
        gpx-location=../src/gpxoverlay/assets/activity_9090497773.gpx \
        x=790 \
        y=10 \
    ! gpxoverlay \
        script-location=../src/gpxoverlay/assets/test.js \
        gpx-location=../src/gpxoverlay/assets/activity_9090497773.gpx \
        x=10 \
        y=10 \
    ! videoconvert \
    ! gtksink

 

  • Patīk 1
Link to comment
Share on other sites

  • 1 month later...

Vispār priekš šādiem pasākumiem ir tāds labs softs: DashWare. Absolūti bezmaksas. Var taisīt savus widgetus, izmantot jau esošos, pielāgot tos pēc sirds patikas. Datu formātus arī atbalsta kaudzēm. Gan tādus, kas pa taisno no visādiem fitnesa pulksteņiem, gan no GoPro, utt. beidzot ar visādiem xml. 

Link to comment
Share on other sites

Tas Dashware diezgan drausmīgs. Garmin Virb Edit vēl daudzmaz.

 

Bet nu visa doma jau pārcelt visu uz pa taisno uz kameru, la i pēctam nav jātaisa post-edit.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...