From 03840af97535b1918108f984d6285efbd5ac1342 Mon Sep 17 00:00:00 2001 From: Tianzi Cai Date: Mon, 26 Aug 2019 14:15:11 -0700 Subject: [PATCH] [video] Streaming AutoML Classification (#2313) * Video Intelligence Beta - Streaming/Live Streaming support for AutoML custom models * add test skeleton * skeleton * more skeleton code * update sample: update video codec/test/model_id/etc. * lint * mask project id * Noah's and Rebecca's suggestions --- video/cloud-client/analyze/beta_snippets.py | 88 +++++++++++++++++- .../analyze/beta_snippets_test.py | 13 ++- video/cloud-client/analyze/requirements.txt | 2 +- .../analyze/resources/googlework_short.mp4 | Bin 1490943 -> 1490997 bytes 4 files changed, 100 insertions(+), 3 deletions(-) diff --git a/video/cloud-client/analyze/beta_snippets.py b/video/cloud-client/analyze/beta_snippets.py index 2039b215c567..1b11020e7d17 100644 --- a/video/cloud-client/analyze/beta_snippets.py +++ b/video/cloud-client/analyze/beta_snippets.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2017 Google Inc. All Rights Reserved. +# Copyright 2019 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -36,6 +36,9 @@ python beta_snippets.py streaming-annotation-storage resources/cat.mp4 \ gs://mybucket/myfolder + + python beta_snippets.py streaming-automl-classification resources/cat.mp4 \ + $PROJECT_ID $MODEL_ID """ import argparse @@ -629,6 +632,79 @@ def stream_generator(): # [END video_streaming_annotation_to_storage_beta] +def streaming_automl_classification(path, project_id, model_id): + # [START video_streaming_automl_classification_beta] + import io + + from google.cloud import videointelligence_v1p3beta1 as videointelligence + from google.cloud.videointelligence_v1p3beta1 import enums + + # path = 'path_to_file' + # project_id = 'gcp_project_id' + # model_id = 'automl_classification_model_id' + + client = videointelligence.StreamingVideoIntelligenceServiceClient() + + model_path = 'projects/{}/locations/us-central1/models/{}'.format( + project_id, model_id) + + # Here we use classification as an example. + automl_config = (videointelligence.types + .StreamingAutomlClassificationConfig( + model_name=model_path)) + + video_config = videointelligence.types.StreamingVideoConfig( + feature=enums.StreamingFeature.STREAMING_AUTOML_CLASSIFICATION, + automl_classification_config=automl_config) + + # config_request should be the first in the stream of requests. + config_request = videointelligence.types.StreamingAnnotateVideoRequest( + video_config=video_config) + + # Set the chunk size to 5MB (recommended less than 10MB). + chunk_size = 5 * 1024 * 1024 + + # Load file content. + # Note: Input videos must have supported video codecs. See + # https://cloud.google.com/video-intelligence/docs/streaming/streaming#supported_video_codecs + # for more details. + stream = [] + with io.open(path, 'rb') as video_file: + while True: + data = video_file.read(chunk_size) + if not data: + break + stream.append(data) + + def stream_generator(): + yield config_request + for chunk in stream: + yield videointelligence.types.StreamingAnnotateVideoRequest( + input_content=chunk) + + requests = stream_generator() + + # streaming_annotate_video returns a generator. + # The default timeout is about 300 seconds. + # To process longer videos it should be set to + # larger than the length (in seconds) of the stream. + responses = client.streaming_annotate_video(requests, timeout=600) + + for response in responses: + # Check for errors. + if response.error.message: + print(response.error.message) + break + + for label in response.annotation_results.label_annotations: + for frame in label.frames: + print("At {:3d}s segment, {:5.1%} {}".format( + frame.time_offset.seconds, + frame.confidence, + label.entity.entity_id)) + # [END video_streaming_automl_classification_beta] + + if __name__ == '__main__': parser = argparse.ArgumentParser( description=__doc__, @@ -678,6 +754,13 @@ def stream_generator(): video_streaming_annotation_to_storage_parser.add_argument('path') video_streaming_annotation_to_storage_parser.add_argument('output_uri') + video_streaming_automl_classification_parser = subparsers.add_parser( + 'streaming-automl-classification', + help=streaming_automl_classification.__doc__) + video_streaming_automl_classification_parser.add_argument('path') + video_streaming_automl_classification_parser.add_argument('project_id') + video_streaming_automl_classification_parser.add_argument('model_id') + args = parser.parse_args() if args.command == 'transcription': @@ -700,3 +783,6 @@ def stream_generator(): detect_explicit_content_streaming(args.path) elif args.command == 'streaming-annotation-storage': annotation_to_storage_streaming(args.path, args.output_uri) + elif args.command == 'streaming-automl-classification': + streaming_automl_classification( + args.path, args.project_id, args.model_id) diff --git a/video/cloud-client/analyze/beta_snippets_test.py b/video/cloud-client/analyze/beta_snippets_test.py index c6b7f7069e68..5c9e533341c9 100644 --- a/video/cloud-client/analyze/beta_snippets_test.py +++ b/video/cloud-client/analyze/beta_snippets_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2017 Google, Inc +# Copyright 2019 Google, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ from six.moves.urllib.request import urlopen import time +import os import uuid import beta_snippets @@ -160,3 +161,13 @@ def test_track_objects_gcs(): assert text_exists assert object_annotations[0].frames[0].normalized_bounding_box.left >= 0.0 assert object_annotations[0].frames[0].normalized_bounding_box.left <= 1.0 + + +@pytest.mark.slow +def test_streaming_automl_classification(capsys, in_file): + project_id = os.environ['GCLOUD_PROJECT'] + model_id = 'VCN6363999689846554624' + beta_snippets.streaming_automl_classification( + in_file, project_id, model_id) + out, _ = capsys.readouterr() + assert 'brush_hair' in out diff --git a/video/cloud-client/analyze/requirements.txt b/video/cloud-client/analyze/requirements.txt index ba28944b6523..a6412f7f2acf 100644 --- a/video/cloud-client/analyze/requirements.txt +++ b/video/cloud-client/analyze/requirements.txt @@ -1,2 +1,2 @@ -google-cloud-videointelligence==1.8.0 +google-cloud-videointelligence==1.11.0 google-cloud-storage==1.14.0 diff --git a/video/cloud-client/analyze/resources/googlework_short.mp4 b/video/cloud-client/analyze/resources/googlework_short.mp4 index be0f40f8ad664bd28537c832f54bfba103230248..30af418a6c5e7bf99aa46951aa005a6ea3481db7 100644 GIT binary patch delta 8744 zcmYk>b(j-p+wkFEWHORuGBeo(C{o;^Kyi16qD70lE?(e);EOvfUfgM+$YMo{yA)ZR z;##DImKMIN-*LR}^Zs!klU(peF)E+!i#h8>Lk1w2Y*z8cRS&)rSNwq_!Pm#bX?hb;mSD}SFT&Q z@;=3tuP&}HbKoj?3|HYIxQfXteiT=!{kY1Mz*RmIt_m}VxhmJfRb>aRY6WrCc#o@A zXIvwm;TkmqSHeqN<1*tKe-qcF7r3UxH{oWtfdrAH)Ev^q&a2M%-yY%n4D=fraTftp-AMVCeaDS5qck8^k+u67~xp8+n zin~Wc-0|sf_i2Z_?_J#eTjL(s9QUASxQ86T9czR8$NRVs#Zu!wJO}r&(YTMl#C`f8 z?z1u67Z&0Er7G?#6L2R_#C=@~+}e)&P95C$%HY0#9`~cYxSv$UqZGlT)x%@hcmmt; znAz~y-{5hc<4IW!Pb!I}-hn6G6+G#i;>qO0lX)(lY_svia_D$+CgRC+4bK;2@QfOa zXN-YoTn;?rC*YY>9na)Dc&2v6Govh?ncMIr?!`0rYdi}|;92Ct^Ic;+ON--Kwh7Nl z$ypu7v-Tf6hgah{o)yoD7@m`_@tjSz?(fg-W*5p=6;L!i=BACl-GqH<1JPd zZ>gbp%g8GK32#M-SGkL~S}(jcJa}vUjkjk7yuHid?W^G(kUxg^+lhDwN`wEL!#gwq z?{H}{VjbSmEAb}O#yh?!-U*3#CpEx3wKU#oTk+1Ejd#;>yj%9--Bto`(iyxvcj4Ww z;@xZD-8Ta7!6A4LWyO0~Y91Sg_k@M_bP(^EwRq2u#Czc--pjEdinR^pv&tx+mqp3& z2TImCDEX(N6n=nG>@iBo|DaTPhEgLJ%2$n1>aId*aR{aLBa{w#QTp9Q85Bhs+8$%~ry8`LaoP;<>h%`4Y0Bd8_E#ZW8AW+gdX^&)D$6{w94 zp*DA;wvhLoZ=-e}h}y3N>fltU^7WyPU5`5H1M2jjQ0J#bU0N7*%^TDWiKv@CqHYhP z?v};}_n{s=j(TDM>bYE~SEr+1Uy1r_XVk~i?`0X(-(%mQ{^>(=Uq#bKpaln^S-a3u zyh2Mm5-rO!wA|Iv3SC1h_B~qJ=4h2tp;g(7R?~%6?+seRQ)o@4oU!Q=?aGj$VB%dd)@Xb;_aF zYlPmYD|(X)=*^y>x0IwdyU{y5NAI*Ay<0K#o^rnTdG!8M(ZAh~zN|F*54q7dOT%qZ z^c`E#k6uMTSswjt2lVqB&~M7#Z5i}l*BJUMY5q1R`n!qff8N4SuV5I*G0avN)?AGA zlQ1&x!N~d%Bj*no1;${M>4{P9FN{hvFsdxXsB2<0_!Fa%7o%w{jJBIGIu6F@ya1!C zBVjf7g=u`_I7tpJ(`o6~jNe3;yXo{BsWAUj+W8{qS#Gi+{&w`1h{Ie@L$9 z%HY2y1KsU`|4ASGuWJ#Y4*_EXv4B;SK#9TxDtQQ0TSq{Ccm!(SBhWC3K&!t9w9P}H zeJcW;4iV_lk-z|XKg>s9WJ3Z8M+i(#CNMidAn`GQMZXbPF^0hUssuL6ncZ~=9GFbt z*aQL>hZ4A)jX?5z0@s%ixW9?Oqkb_0PudZ9E=AtR!9Q~l_+$~(=Myw15lp2MOud_6 z+KmJ=)FGI=BEfu}2^OeNu+TPwWd;!}ZxgK4pJ0^%1nagS*dRN>M%@WEX-}|iCW7rR z6YP|YVAnMS`^+NvEd&Q$AUO08!O^iO!GsS4CrXjY=?PAMMQ~Pif^#L|LPLU=+Y!7f z3CSY}-snN_&MAWTW&go0f{$f@=PL=m%tP?iX@YOQ!}JWqR61g6b1;o;nAT8CN3P*d zn9;v5<^SJIdj#|IMwl7LV8*fx$IRx!lwYN0-d&jaCGpFzF^lfOEFQ%ywFR?mb<7Io zF)K^)s*f;h^~U__6lQ~OFdJUPY*Yuc`60{}T`}7p#%#Y1vy(LG(gm|;7R>mqn0=mN z_J4yp@D=8ebkaCK=E#!7%!I3$<0NTf66WO5m@`jf&X%Hc|G}I;7<0*H%wOZH%`)3LsuWf``S3p*&+ zP(y6%0d{0Ec8X@$pY6p?(+xX)d+dzqu(RgC&UOqt$1LnNvfuW1?2h@cJ4>x@x3GH# zV%YIpu>01+?sout-~jBwA?%@-v4?fQ9+d%m^c3u|Ut>>bggvPq_S75LGcse(PR5>d z3VYsF>_vmI7ca$LUIcr^S?o13+`59;-#5YDC=GvXhrM+Sc2X^l>&@72M6D21PFroGK4+YUIGFxee#5nK<<% zw?So`M$)9|VVo9joR-6I+ML8`pN!K{a=Yxt=~fdbehE(Rx;XuY5OW5U!Wmc{XNUx2 zS#U;X#u>E^XIyEV@y~E3YdBMW!@Yc$N556RP`cJ~w788E5oABFxgg^Bo&VEE( zmJGyw`H8r)^@*z)Ph2Z6alHl+H=;Fhqec>!*oC-NeTiFpm$=Qji8~Pcnz&2LiF^2l zxOe-A_`f3Z*)by7dJ-vmf=Io3L>gu!(s)0Srdx=_e?z2qeIor96B(39Wa0}V^TrWb zeU8YEenfV)BywyrkzaZfxm}&e8#hr_64mDtwM!6<>qj*0G@`kV5H0*Q(VUgq5p8gv zXy;-?yPqf8^BB=S>4=W$OLYEhqB}J}~GT98`Q4l#{~W8twP#_?8+L%idKSjVemF^<_{9QDLFPK$AD6ywMz#*q?#|*E zUE7OsY!u@V-{>QB(f73&M-MTM&bSA=#5m62{y{k7U~HZsM@cab2^^b^`@~5xjyqx; z>%=&2igC=xeKoTf$8#}`{bC#s#W*^Nanu##xFyDM5|3w}7)KW|j@DuvMa4J{iE(ri z<5-9%GFFU3Oe58AVjL&LI3D6j-%*SsyBNnR@r`WD@Z=Cm$hlFB;}M?W4a7L6igBbA z<0ve~F-MG}yBNnyF^+G=IBJP;NYlhqVjOM7II4{FJ)T2f z;W;8?alE7$$9z1eWSu=P#?ecRL-6JDO!1B6X?U*J5#zWa#vyQWH=P)Vh|L25ktdnO zI5y!;I~MQf#l<*;1~LgpWa%Qt@eSUb1@Yz;f+!&LP*BHP9Vqq)uWSHN`j%;N4hCjAMrw$0;!mft94|VjN;5yCY&8(rDilF^(Bx95UqL znPME1#5gjGaikXGND||iiuYp7gAx+gNcjXMO-GdUbx<-jLCNQ!e0db5Xgo>@H%i4* zDAo3%)NGDYM`F!GD6J-;v@L_ucO1&M?NJ8*>lM>c5>}#2_y#3$6Uu@FltmvhZ1{35$Cpotp@_uFUBFj)mKcXzYj(lcPi0@r{u>zOgOwjh91{CgYnv6yGd?l=Xron-AjKE=_ledHggM-{F<` zj$gxfCW!BxG`ap7-<>S@?pMI~s0F@PP4N9u2;ZN>P`yGIdKT1RBUEcX>StY1GnYlp zz8N)73eX)@}ytf;q} zpgxlLvv}0EF;L$L8;}-FEs16nLJQ?Vi#vpt`Xic%o|f}ITET8;gy#=9WfV z{0S|#@g~}q^JqzeAG=PX9TFHhwg>H`*u|Nj&@SIVyC(ZL9kkoM(Vpf+dm+VMr$l?( z744sA=$Pp4#^}lc^uRiFO9;Xd--v#Ro@NNTd{OHeg%>itN6&Q}J#P>_|0*$#!{Qso z*P@pak0?_?j6(pV$_?}y)zNE-UDP>&-k=$J;|AzW#W|XnMQ>dLy`3MuV{P=#-OzjZ z(0fU~JUZ$F1XvaesBGAXzHv4BmP9cQv5h0u(2q|$Z-vS!TR`%?7?3= zjK8!Sf8|d2YZSs?TfC!QTl~!%;cxXj{`O)Uon-a1@ej(2f9N^2lUkOy$OrY{q z0@d>os9BmogR%r#_zARrC&sZ+jN=%AZhsQ!Tc5ztnFNOQATXvWfeF75n7Kxbqc?#C z;vCD<5m-}_z@~`=cKnCH{_F&fNR4wV2>h}^jAJf=Yo8IgE3t>F_{Jk?{^UA=*C`16 z(VW0PlL`91Cm2jcFe;w$Sq6e>gcQ=%BAD|C!8{3K9Ph+9o)IkdfM8j%kMd*1IBpZH z9V1vzavOXn#_>DBRt*TY6AS4eM$+j9!Cpcc{UQVh<{&uuEy0noRRqT@Bsea&7{>;J zQ^h-GTqQVL8lU@{;Ki9@98&D6BqfVw+-^c%(4_S$9v2?mBl!c z#W=QO77`99dL6SwcFfYk4dot)akRy(BDPVz7G`ZBg}T$lI3|d3JjZOZLX1Psw9bv$ zF0&ZNH8Bohh;B{9ILcx69);Q0!2EV6=AfV$#}&+BZNxXm$Yf)s;J6oJ92YRBOMzKE zF%#v-$~;M2^eg7lrkKkmzTzb2dTI3iGt3|5{pQ`6JI;x52+aI66?2~q^z(AeqqZ2w zAu*0WG0)b-JnzB0oI#93{v~co&D(v%IL?c4>=)m7l2(jkBIb)5VjME{hmM$^#4$K8 z#v!brToL2gBgPREgseD8>;J(^$Bl(9%nUR<|Lv(IoVv0LYe?gm!xf z?Hwh?A%zayB6L!kpI#!yaaoLGI-%sgVjT4dJ&^e03WT1jgkCft^v762e-#qr5cl}> zIhHb4jN>mcjx%B$1F`IJSkYWqsl_~EpXb5Kcmpd-cdYE?#W-4F<&pRKZi{i;#OksI zt7k8)UZ=47rN$Z{1Tich*2tf+68^>-{{(Bs2CO;Tu;#DCT3i`xwT-o2a(|eHwRr;8 z4xx+PZ?X3E6ytb`bzJgJAH_PqT6{zP{%GB}hILyGKak0u2%bDUjrChsthWhb9DA`o z+!EuEr+rr%F^;3y+Bh+eHDVk$#W+S|J1@mJ9*S{H663goop!ev$29CrLJL`iDY93< zZYlQBW;u4dF4&z!3pzi=?%qXwqh}`UUj4;5#2)$!H4GFsknc@<=ws~R<;6HkV~;r~ z#t|>ZA(k=eIrg*_*faguiFL8(4#J+_6Z^a3*h}Qh^4i!d7m9K05aU=R#_==uCa)NW z7|2!`GD!xJ&x!V7sdX%NM3AEr_9>b2tXquZ6810UurFKK$#Q2m;;?TU*mr*uUlhWu%83QB<@Bg8nC;*=aG#<2&d zLO_f|&Q;aLIO^ckPASGA1?xQ)UDKy|c&LBx1BE^RZpo|ni86$+q;&GO`#W>!Hafp?yDJ{m)5a)XVnoTmm=AamdoKJcq#xYrpV-Mk!6A9=3i*UZu z;v4eODqO@QTy7BI+B*n0pG&x_7)PI936ID~c;ZUJbC(caeS+|ha`r$4!YAGkPR>jC z;T6Jfsu2F@C(ab#$h3*L{5o-^6N#&Kjkp#<7x7t$i*+P!WRSR-Es0y{6XW}ICRUophjTpy0B8S%zx$rlU8|{g_8cgI^Ji#D#mKO|_zV3lz(nKVQD#=|`t`ZB6gi3num| zJZZrvwxSu$ytXU+fbs4juE8%!qQ&uSHcv)*hsigfe%kNGaIMk{tJHstm Swk$_hSXNxp@!^rb*Zn_78zQLy delta 8680 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 G(Ek9nOZsjA