<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Quốc Huỳnh on Medium]]></title>
        <description><![CDATA[Stories by Quốc Huỳnh on Medium]]></description>
        <link>https://medium.com/@huynhquoc?source=rss-589af7f792d1------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Xa-1LOLjJ0ZcJYyhWlqv7Q.jpeg</url>
            <title>Stories by Quốc Huỳnh on Medium</title>
            <link>https://medium.com/@huynhquoc?source=rss-589af7f792d1------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 23:01:20 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@huynhquoc/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Một số ghi chú khi làm việc với video editing, pre-template video trên mobile (Flutter, android…]]></title>
            <link>https://medium.com/@huynhquoc/m%E1%BB%99t-s%E1%BB%91-ghi-ch%C3%BA-khi-l%C3%A0m-vi%E1%BB%87c-v%E1%BB%9Bi-video-editing-pre-template-video-tr%C3%AAn-mobile-flutter-android-45f0cadd1662?source=rss-589af7f792d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/45f0cadd1662</guid>
            <category><![CDATA[video-customize]]></category>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[pre-template-video]]></category>
            <category><![CDATA[ffmpeg-mobile-apps]]></category>
            <category><![CDATA[ffmpeg]]></category>
            <dc:creator><![CDATA[Quốc Huỳnh]]></dc:creator>
            <pubDate>Sat, 04 Mar 2023 06:39:47 GMT</pubDate>
            <atom:updated>2023-03-04T07:54:08.639Z</atom:updated>
            <content:encoded><![CDATA[<h3>Một số ghi chú khi làm việc với video editing, pre-made template video trên mobile (Flutter, android, iOS)</h3><blockquote><em>Chỉ là note của cá nhân sau quá trình làm để tránh quên ;) ko phải chia sẻ hướng đi;)))</em></blockquote><h3>ABR (adaptive bitrate streaming)</h3><ul><li>Khi làm màn hình dạng video feed như Instagram, capcut có rất nhiều video (resolution, quality khác nhau) nếu dùng 1 file gốc để display thì hiệu năng sẽ trở nên rất kém (ứng dụng sẽ nặng lên, tốc độ load từng video sẽ rất chậm tạo cảm giác khó chịu đến người dùng khi tình trạng network kém). Dùng ffmpeg để generate ra file m3u8 (bao gồm các audio theo từng băng thông) hoặc dùng 3th party (AWS….)</li></ul><h4>ffmpeg</h4><ul><li>concat multiple image/video into one video</li><li>add watermark</li><li>transition giữa các clip (xfade, gif, webm, webp…)</li><li>stickers, frame, filter nằm trên bề mặt của mỗi clip</li><li>ghép nhạc vào video</li><li>trim video/image (có 1 tip nhỏ là khi người dùng thêm image thì chúng ta convert nó sang video bằng ffmpeg luôn, đưa về đơn vị video hết. quá trình convert ko tốn thời gian quá lâu)</li><li>animation text: text có animation</li><li>canvas text: text đơn giản có font, có background, shadow (kiểu như subtitle)</li></ul><h4>transition</h4><ul><li>xfade transition: slide, circle crop, fade ….</li><li>advanced transition: 1 số transition mà có thể từ 1 format cụ thể nào đó có thể là png, webm, gif</li></ul><h4>overlay/filter</h4><ul><li>filter</li><li>sticker</li><li>text</li><li>picture in picture</li></ul><h4>pre-made template</h4><ul><li>định nghĩa 1 jsonTemplate hoặc 1 kiểu dử liệu nào đó để cá nhân hệ thống sử dụng</li></ul><pre>{<br>   &quot;ratio&quot;: &quot;916&quot;,<br>   &quot;timeline&quot;: {<br>        &quot;slides&quot;: [<br>           {&quot;id&quot;: 1, &quot;startTime&quot;: 0, &quot;endTime&quot;: 10.0, &quot;duration&quot;: 100.0 , &quot;angle&quot;: 90,&quot;filePath&quot;: &quot;&quot;, &quot;width&quot;: 100, &quot;height&quot;: 100, &quot;rect&quot;: {&quot;l&quot;: 0, &quot;t&quot;: 0, &quot;b&quot;: 100, &quot;r&quot;: 100}} <br>        ],<br>        &quot;audio&quot;: [<br>           {&quot;id&quot;: 1, &quot;filePath&quot;: &quot;&quot;, &quot;startTime&quot;: 0, &quot;endTime&quot;: 10.0, &quot;duration&quot;: 100.0, &quot;slideId&quot;: 1}<br>        ]<br>    },<br>    &quot;overlays&quot;: [<br>          {&quot;id&quot;: 1,&quot;type&quot;: &quot;TEXT&quot;, &quot;filePath&quot;: &quot;&quot;, &quot;startTime&quot;: 0, &quot;endTime&quot;: 10.0, &quot;duration&quot;: 100.0, &quot;slideId&quot;: 1}<br>          {&quot;id&quot;: 2,&quot;type&quot;: &quot;CANVAS&quot;, &quot;filePath&quot;: &quot;&quot;, &quot;startTime&quot;: 0, &quot;endTime&quot;: 10.0, &quot;duration&quot;: 100.0, &quot;slideId&quot;: 1}<br>          {&quot;id&quot;: 3,&quot;type&quot;: &quot;STICKER&quot;, &quot;filePath&quot;: &quot;&quot;, &quot;startTime&quot;: 0, &quot;endTime&quot;: 10.0, &quot;duration&quot;: 100.0, &quot;slideId&quot;: 1}<br>    ],<br>    &quot;transitions&quot;: [<br>      {&quot;id&quot;: 1,&quot;type&quot;: &quot;xfade&quot;, &quot;name&quot;: &quot;fadeWhite&quot;, &quot;filePath&quot;: &quot;&quot;, &quot;startTime&quot;: 0, &quot;endTime&quot;: 10.0, &quot;duration&quot;: 100.0, &quot;slideId&quot;: 1}<br>      {&quot;id&quot;: 2,&quot;type&quot;: &quot;advance&quot;, &quot;name&quot;: &quot;HEART_RAIN.webm&quot;, &quot;filePath&quot;: &quot;&quot;, &quot;startTime&quot;: 0, &quot;endTime&quot;: 10.0, &quot;duration&quot;: 100.0, &quot;slideId&quot;: 1}<br>    ],<br>    &quot;frames&quot;: [<br>       {&quot;id&quot;: 1, &quot;name&quot;: &quot;HEART_RAIN.webm&quot;, &quot;filePath&quot;: &quot;&quot;, &quot;startTime&quot;: 0, &quot;endTime&quot;: 10.0, &quot;duration&quot;: 100.0, &quot;slideId&quot;: 1}<br>    ],<br>}</pre><h4>camera filter</h4><ul><li>Color filter</li><li>LUT filter</li><li>Android: GPUImage</li><li>iOS: Metal</li></ul><h4>Performance tip</h4><ul><li>chỉ thao tác xử lý trên video 720p 1080p hoặc nhỏ hơn, hạn chế xử lý trực tiếp trên video 4k, vì có thể dẫn tới crash nếu memory ko đáp ứng (trim, multiple 4k video, generate thumbnail ….)</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=45f0cadd1662" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Flutter screen from native (Android/ iOS) and opposite]]></title>
            <link>https://medium.com/@huynhquoc/open-flutter-screen-from-native-android-ios-and-opposite-eea6c57f13ee?source=rss-589af7f792d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/eea6c57f13ee</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[mobile-development]]></category>
            <dc:creator><![CDATA[Quốc Huỳnh]]></dc:creator>
            <pubDate>Fri, 14 Jan 2022 12:12:29 GMT</pubDate>
            <atom:updated>2022-01-14T12:16:31.686Z</atom:updated>
            <content:encoded><![CDATA[<p>When we open Flutter screen from native code??</p><ul><li>When you already have a Android/iOS native app you want to add more module and use Flutter framework for fast development.</li><li>When you have a flutter application which need to handle some feature on Native (Camera filter, apply water mark to camera, custom camera…) But the native screen need to open Flutter screen again. (Flutter screen -&gt; Native screen -&gt; Flutter screen) (3 screen layers)</li></ul><p><strong>1/ Open Flutter screen from Android native:</strong></p><ul><li>Create Activity class from Android native code</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/962cab833930216fcbdc0cb39a0288bc/href">https://medium.com/media/962cab833930216fcbdc0cb39a0288bc/href</a></iframe><p>And then, only need to start the Flutter activity above, by using:</p><pre>startActivity(NewFlutterActivity()<br>        .initialRoute(&quot;/yourFlutterRoute&quot;)<br>        .build(this)))</pre><p><strong>2/ Open Flutter screen from iOS native:</strong></p><ul><li>Create ViewController from iOS swift native</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cf286574b3ae3d185eeb1205238d8a0e/href">https://medium.com/media/cf286574b3ae3d185eeb1205238d8a0e/href</a></iframe><p>And then, only need to start the Flutter activity above, by using:</p><pre><strong>let</strong> flutterViewController = NewFlutterViewController(project: <strong>nil</strong>, initialRoute: &quot;/yourFlutterRoute&quot;, nibName: <strong>nil</strong>, bundle: <strong>nil</strong>)</pre><pre><strong>let</strong> appDelegate = UIApplication.shared.delegate <strong>as</strong>! AppDelegate</pre><pre>appDelegate.navigationController?.pushViewController(flutterViewController, animated: <strong>true</strong>)</pre><p><strong>3/ Flutter open Native screen ??</strong></p><p>In flutter you can create a MethodChannel and then use this MethodChannel to invokeMethod to native. In native code you need to listen and start screen.</p><ul><li>Flutter code:</li></ul><pre>static const MethodChannel <em>_channel </em>=<br>    const MethodChannel(&#39;flutter.open.native.screen&#39;);</pre><pre><em>final resultFromNative = _channel.invokeMethod(&quot;openNativeScreen&quot;)</em></pre><ul><li>Android native code:</li></ul><pre>MethodChannel mNativeChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, &#39;flutter.open.native.screen&#39;)<br>mNativeChannel.setMethodCallHandler <strong>{ </strong>call, result <strong>-&gt;<br>    </strong>when (call.method) {<br>        &#39;<em>openNativeScreen</em>&#39; -&gt; {<br>           // startActivityHere<br>           // use result to send data back to Flutter, by using: result(&quot;hello flutter&quot;)</pre><pre>        else -&gt; {<br>            result.notImplemented()<br>        }<br>    }<br><strong>}</strong></pre><ul><li>iOS native code:</li></ul><pre>mNativeChannel = FlutterMethodChannel(name: &#39;flutter.open.native.screen&#39;,binaryMessenger: flutterViewController!.binaryMessenger)</pre><pre>cameraChannel?.setMethodCallHandler({</pre><pre>(call: FlutterMethodCall, result: <strong>@escaping</strong> FlutterResult) -&gt; Void <strong>in<br>if</strong>(call.method == &quot;openCamera&quot;){<br>   // Open iOS native screen here</pre><pre>   // use result to send data back to FLutter by calling: result(&quot;hello flutter&quot;)<br>}<strong>else</strong>{<br>     result(FlutterMethodNotImplemented)<br>}})</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eea6c57f13ee" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build a camera filter application on iOS using Swift]]></title>
            <link>https://medium.com/@huynhquoc/build-a-camera-filter-application-on-ios-using-swift-faa8cea8582c?source=rss-589af7f792d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/faa8cea8582c</guid>
            <category><![CDATA[swift-programming]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[metal]]></category>
            <dc:creator><![CDATA[Quốc Huỳnh]]></dc:creator>
            <pubDate>Sat, 23 Oct 2021 10:16:30 GMT</pubDate>
            <atom:updated>2021-10-23T10:22:21.878Z</atom:updated>
            <content:encoded><![CDATA[<ul><li>Metal: render camera, custom filter (Metal Shading language <a href="https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf">https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf</a>)</li><li>CIImage, CIFilter: apply filter, add water mark…</li><li>AVAssetWriter: setup Video, Audio input and output</li><li>AVAssetWriterInput: use to append the sample buffer when the camera callback in <strong>captureOutput </strong>function<strong> (remember implement delegate AVCaptureVideoDataOutputSampleBufferDelegate)</strong></li></ul><p>Use case</p><ul><li>apply filter to camera realtime (live camera preview with multiple filters)</li><li>add watermark and save video with watermark</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=faa8cea8582c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build the video list like TikTok using Flutter framework]]></title>
            <link>https://medium.com/@huynhquoc/build-the-video-list-like-tiktok-using-flutter-framework-605def62c30e?source=rss-589af7f792d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/605def62c30e</guid>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[flutter-tiktok]]></category>
            <category><![CDATA[flutter-video-player]]></category>
            <category><![CDATA[flutter-lottie]]></category>
            <dc:creator><![CDATA[Quốc Huỳnh]]></dc:creator>
            <pubDate>Sun, 03 Oct 2021 17:46:48 GMT</pubDate>
            <atom:updated>2023-06-16T03:32:04.107Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>This is my idea maybe it is not good for everyone. Actually we have many way to handle this screen, maybe it better than my idea!</blockquote><figure><img alt="Image" src="https://cdn-images-1.medium.com/max/522/1*J6sq3AMy3a4xMQxXXyHRGQ.png" /></figure><h3>How to create the screen like the picture above?</h3><p><em>I will list for you some things I used to create the screen like Tiktok Video List using Flutter</em></p><p><em>In this topic I use:</em></p><ul><li><strong>PageView</strong>: show video list in vertical direction, preload next video when the current video hide 70% of screen</li><li><strong>VideoPlayer</strong>: play the video file for each video, pause and play, tracking the progress.</li><li><strong>VisibleDetector</strong>: in case when the screen disappear we need to stop the video controller (the coming call, user open another screen when push notification come)</li></ul><h3>PageView</h3><ul><li>First of all you need to create a <strong>PageController</strong> to manage the <strong>PageView</strong></li></ul><pre>final PageController <strong>_pageController</strong> = PageController(initialPage: 0, keepPage: true);</pre><pre>/// Remember add the scroll listener<br>_pageController<strong>.addListene</strong>r(_scrollListener);</pre><pre><strong>/// [keepPage]</strong> keep the item widget to prevent rebuild</pre><ul><li>Create some variable to handle page change logic</li></ul><pre>int <strong>_currentPage</strong> = 0;<br>bool <strong>_isOnPageTurning</strong> = false; // flag to check the video item in on playing or not</pre><ul><li>It is so simple to use <strong>PageView</strong> by calling the dart code below</li></ul><pre>PageView.builder(<br>  scrollDirection: Axis.vertical,<br>  controller: <strong>_pageController</strong>,<br>  itemCount: items.length,<br>  itemBuilder: (context, index) {<br>    return <strong>VideoItemWidget</strong>(<br>      videoInfo: items[index],<br>      pageIndex: index,<br>      currentPageIndex: <strong>_currentPage</strong>,<br>      isPaused: <strong>_isOnPageTurning</strong>,<br>      videoEnded: (){},<br>    );<br>  },<br>)</pre><ul><li>To preload and play the next video when the current video disappear about <strong>70%</strong>, let do it by the logic below.</li></ul><pre>void _scrollListener() {<br>  if (_isOnPageTurning &amp;&amp;<br>      _pageController.page == _pageController.page!.roundToDouble(){</pre><pre>    setState(() {<br>      _currentPage = _pageController.page!.toInt();<br>      _isOnPageTurning = false;<br>    });</pre><pre>  } else if (!_isOnPageTurning &amp;&amp;<br>      _currentPage.toDouble() != _pageController.page) {</pre><pre>    if ((_currentPage.toDouble() - _pageController.page!).abs()&gt;<strong>0.7</strong>{<br>      setState(() {<br>        _isOnPageTurning = true;<br>      });<br>    }</pre><pre>  }<br>}</pre><h3>Those things above are finish to create the PageView to show the List of Video</h3><p>Now let continue going to the <strong>VideoItemWidget widget.</strong></p><ul><li>Create a StatefulWidget class, and remember put the <strong>const </strong>into the <strong>contrucstor</strong> to <strong>prevent</strong> Flutter <strong>rebuild</strong> this item</li></ul><pre>class VideoItemWidget extends StatefulWidget {<br>  final int pageIndex;<br>  final int currentPageIndex;<br>  final bool isPaused;<br>  final void Function()? videoEnded;</pre><pre>const VideoItemWidget(<em>{    <br>    </em>required this.videoInfo,<br>    required this.pageIndex,<br>    required this.currentPageIndex,<br>    required this.isPaused,<br>    this.videoEnded,<br>});<br><br>@override<br>State&lt;StatefulWidget&gt; createState() =&gt; _VideoItemWidgetState();</pre><ul><li>Every single <strong>VideoItemWidget </strong>will keep their <strong>VideoController</strong> in its <strong>State, </strong>So we need to init the <strong>VideoController </strong>in<strong> initState(), </strong>and we <strong>must</strong> dispose the<strong> VideoController </strong>in <strong>dispose().</strong></li><li><strong>To make more performance with the network condition (Please use </strong><strong>Adaptive bitrate streaming)</strong></li></ul><pre>class _VideoItemWidgetState extends State&lt;VideoItemWidget&gt;{ </pre><pre>  late VideoPlayerController? _videoPlayerController;</pre><pre>@override<br>  void <strong>initState</strong>() {<br>     <strong>super.initState</strong>();</pre><pre>     _videoPlayerController =<br>     VideoPlayerController.network(/*Video URL*/);</pre><pre>     _videoPlayerController?.addListener(_videoListener);</pre><pre>     _videoPlayerController?.initialize().then((){});<br>  }</pre><pre>@override<br>void <strong>dispose</strong>() {<br>  if (initialized &amp;&amp; _videoPlayerController != null) {<br>    _videoPlayerController?.removeListener(_videoListener);<br>    _videoPlayerController?.dispose();<br>    _videoPlayerController = null;<br>    }<br>  <strong>super.dispose</strong>();<br>  }</pre><pre>}</pre><ul><li>Now let go to build the item UI for the Video</li></ul><p>Firstly you have to check that the video is <strong>Landscape</strong> or <strong>Portrait, </strong>If the video is<strong> Portrait</strong> we render the video with<strong> BoxFit.cover, </strong>if the video is<strong> Landscape</strong> we render with <strong>BoxFit.fitWidth (</strong><em>These is my opion render you can customize you own</em><strong>)</strong></p><pre>// To check landscape or portrait it so simple</pre><pre>bool <strong>isLandscapeVideo</strong> = video.width &gt; video.height;</pre><p>Render <strong>Landscape</strong> Video widget, it is simpler than <strong>Portrait</strong> Video. you can use the <strong>aspectRatio</strong> of the <strong>VideoController</strong></p><pre>Widget _renderLandscapeVideo() {<br>  if (!initialized) return Container();<br>  if (_videoPlayerController == null) return Container();<br>  return Center(<br>    child: AspectRatio(<br>      child: <strong>VisibilityDetector</strong>(<br>          child: <strong>VideoPlayer</strong>(_videoPlayerController!),<br>          onVisibilityChanged: <strong>_handleVisibilityDetector</strong>,<br>          key: Key(&#39;key_${widget.currentPageIndex}&#39;)),<br>      aspectRatio: _videoPlayerController?.value.aspectRatio,<br>    ),<br>  );<br>}</pre><p>Render <strong>Portrait</strong> Video, you need to check with the Screen size to calculate the the <strong>aspectRatio.</strong></p><pre>var tmp = MediaQuery.<em>of</em>(context).size;</pre><pre>Widget _renderPortraitVideo() {<br>  if (!initialized) return Container();<br>  if (_videoPlayerController == null) return Container();<br><br>  var screenH = max(tmp.height, tmp.width);<br>  var screenW = min(tmp.height, tmp.width);<br>  tmp = _videoPlayerController!.value.size;<br><br>  var previewH = max(tmp.height, tmp.width);<br>  var previewW = min(tmp.height, tmp.width);<br>  var screenRatio = screenH / screenW;<br>  var previewRatio = previewH / previewW;<br><br>  return Center(<br>    child: OverflowBox(<br>      child: <strong>VisibilityDetector</strong>(<br>          <strong>onVisibilityChanged</strong>: <strong>_handleVisibilityDetector</strong>,<br>          key: Key(&#39;key_${widget.currentPageIndex}&#39;),<br>          child: <strong>VideoPlayer</strong>(_videoPlayerController!)),<br>      maxHeight: screenRatio &gt; previewRatio<br>          ? screenH<br>          : screenW / previewW * previewH,<br>      maxWidth: screenRatio &gt; previewRatio<br>          ? screenH / previewH * previewW<br>          : screenW,<br>    ),<br>  );<br>}</pre><ul><li>Handle pause when the screen disappear and continue playing when the screen open back</li></ul><pre>void _handleVisibilityDetector(VisibilityInfo info) {<br>  if (<strong>info.visibleFraction == 0</strong>) {</pre><pre><strong>/// The screen is disappeared<br>   </strong>if (initialized &amp;&amp;widget.pageIndex == widget.currentPageIndex &amp;&amp;<br>        !widget.isPaused<br>        ) {<br>      if (_videoPlayerController != null) {<br>        _videoPlayerController?.pause().then((value) {});<br>      }<br>    }</pre><pre>  } else {<br>   <strong>/// The screen is appeared<br>   </strong>_videoPlayerController?.play().then((value) {});</pre><pre>  }<br>}</pre><h3>That all to create the screen like tiktok video list, this is my idea, and my practice for learning purpose.</h3><h3>Actually we have many way to handle this screen, maybe it better than my idea!</h3><h3>Let share your idea to make it better. I want i learn more to improve</h3><p>If you want to use it in fast development: you can check my package on pub dev: <a href="https://pub.dev/packages/flutter_video_newfeed">https://pub.dev/packages/flutter_video_newfeed</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=605def62c30e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Một tấm hình được chụp và được nhận dạng như thế nào trên mobile?]]></title>
            <link>https://medium.com/@huynhquoc/m%E1%BB%99t-t%E1%BA%A5m-h%C3%ACnh-%C4%91%C6%B0%E1%BB%A3c-ch%E1%BB%A5p-v%C3%A0-%C4%91%C6%B0%E1%BB%A3c-nh%E1%BA%ADn-d%E1%BA%A1ng-nh%C6%B0-th%E1%BA%BF-n%C3%A0o-tr%C3%AAn-mobile-99e3fce5f2a6?source=rss-589af7f792d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/99e3fce5f2a6</guid>
            <category><![CDATA[computer-vision]]></category>
            <category><![CDATA[image-recognition]]></category>
            <category><![CDATA[newbie-share]]></category>
            <category><![CDATA[newbie]]></category>
            <dc:creator><![CDATA[Quốc Huỳnh]]></dc:creator>
            <pubDate>Sat, 09 Mar 2019 06:44:19 GMT</pubDate>
            <atom:updated>2019-03-09T06:44:19.951Z</atom:updated>
            <content:encoded><![CDATA[<ul><li>Làm sao để biết ảnh đó có con mèo? con mèo nằm ở đâu trong bức hình, con mèo màu gì, nó có phải con mèo hay không?</li><li>Làm sao facebook khi chụp sẽ tự động nhận dạng được có người nào và tạo <strong>tag</strong> người đó ?</li><li>làm sao trang google khi bạn gõ từ khoá <strong>`cat` </strong>thì 1 nùi ảnh liên quan về con mèo nó hiện ra? thậm chí đưa tấm hình có con mèo thì nó sẽ nhận dạng trong hình có con mèo và đi tìm những ảnh có liên quan ?</li></ul><p>================================================================================</p><p><strong>??? LÀM THẾ NÀO ĐỂ GIẢI QUYẾT VẤN ĐỀ NÀY ???</strong></p><p><strong>1/ Quy trình thực hiện:</strong></p><ul><li>Xây dựng mô hình phát hiện và nhận dạng đối tượng trong ảnh.</li><li>Sử dụng mô hình này trực tiếp trong local của thiết bị (<strong>khá tốn kém vì trên mobile bộ nhớ hạn hẹp, có thể sử dụng tensorflow để xây dựng file mô hình phù hợp về bộ nhớ</strong>) <strong>hoặc </strong>Sau khi xây dựng mô hình tiến hành update lên server và ở <strong>client</strong> sẽ gởi request một tấm ảnh lên server, server tiến hành xử lý và trả về kết quả cho <strong>client</strong> (<strong>cần network</strong>)</li><li>Xây dựng một API xử lý nhận ảnh từ phía <strong>client.</strong></li></ul><p><strong>2/ Chi tiết từng quá trình thực hiện.</strong></p><p>2.1 Xây dựng mô hình phát hiện và nhận dạng: Có nhiều cách để phát hiện đối tượng trong ảnh và nhận dạng đối tượng đó. cần tìm hiểu về :</p><ul><li>YOLO detection</li><li>Cascade classifier</li><li>CNN (Convolution neural network)</li><li>Shape and color filter, window search (khá tệ để phát hiện đối tượng bằng cách này :D)</li><li>SVM, kmean, BOW ….</li><li><strong>Pla pla khá nhiều cách để thực hiện xây dựng một mô hình máy học</strong></li><li><strong>**Ở đây mình sẽ chỉ dẫn cách xây dựng từ cơ bản và deloy mô hình lên server để mobile có thể giao tiếp với mô hình này bằng API***</strong></li></ul><p><a href="https://github.com/huynhquoc09x11x1996/sift_bow_libsvm">CODE THAM KHẢO XÂY DỰNG MÔ HÌNH NHẬN DẠNG ẢNH</a> :</p><ul><li><strong>file</strong> huấn luyện mô hình: <a href="https://github.com/huynhquoc09x11x1996/sift_bow_libsvm/blob/master/main_training.py"><strong>main_training.py</strong></a></li></ul><p><strong>1/ Xây dựng mô hình nhận dạng ảnh thông qua:</strong></p><ul><li>Rút trích đặc trưng ảnh bằng SIFT</li><li>Giảm chiều đặc trưng thông qua BOW (bag of visual words)</li><li>Phân loại ảnh dùng SVM</li></ul><p><strong>2/ Phía server: dùng Flask + Python để xây dựng API.</strong><em>(</em><a href="https://github.com/huynhquoc09x11x1996/Python_Flask_API_for_Android"><em>Code tham khảo</em></a><em>)</em></p><p>Cần cài đặt:</p><ul><li><strong>Python</strong>: Ngôn ngữ lập trình phía Server</li><li><strong>Flask</strong>: tạo các <strong>route</strong> để phía client gữi request</li><li><strong>request</strong>, <strong>jsonify</strong>: nhận dữ liệu <strong>request</strong> từ phía <strong>client</strong> và dùng <strong>jsonify</strong> để parse và trả về kết quả cho phía <strong>client</strong></li><li><strong>svmutil</strong>, <strong>opencv</strong>: sử dụng mô hình để nhận dạng ảnh</li><li><strong>joblib</strong>: đọc và ghi tập tin mô hình.</li></ul><p>CHI TIẾT:</p><p>-&gt; Flask: phương thức POST</p><blockquote># POST<br>@app.route(‘/api/post_some_data’, methods=[‘POST’])</blockquote><p>-&gt; dùng request.get_json() để parse trường dữ liệu ảnh gởi từ client. Dữ liệu từ <strong>client là base64 </strong>convert từ ảnh<strong>. </strong>sau khi nhận từ phương thức <strong>POST </strong>với trường dữ liệu ảnh với dạng<strong> base64, </strong>sử dụng <strong>Image.open(io.BytesIO(base64.b64decode(b64str))) </strong>để chuyển<strong> base64 </strong>về dữ liệu <strong>hình ảnh</strong> để cung cấp cho mô hình nhận dạng ảnh</p><p>-&gt; Sử dụng mô hình để nhận dạng ảnh trên để dự đoán ra nhãn của ảnh đó và dùng <strong>jsonify đ</strong>ể gởi kết quả về<strong> client </strong>với dữ liệu dạng<strong> JSON</strong></p><p><strong>3/ Phía client: xây dựng ứng dụng android để nhận dạng ảnh.</strong><a href="https://github.com/huynhquoc09x11x1996/AndroidConnectApiPython/tree/master/app/src/main/java/com/example/quocb14005xx/apipython">(Code tham khảo)</a>:</p><p>Ý tưởng của ứng dụng: xây dựng ứng dụng cho phép nhận dạng 1 tấm ảnh chụp hoặc ảnh trong thư viện.</p><p>===&gt; Đọc ảnh từ <strong>camera</strong> hoặc từ <strong>Gallery:</strong></p><ul><li>Gallery:</li></ul><blockquote>val intent = Intent() intent.type = “image/*” intent.action = Intent.ACTION_GET_CONTENT startActivityForResult(Intent.createChooser(intent, “Select Picture”), PICK_IMAGE_REQUEST)</blockquote><ul><li>Camera:</li></ul><blockquote>val intent = Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE) startActivityForResult(intent, CAPTURE_IMAGE_REQUEST)</blockquote><p>Nhận ảnh sau khi pick từ Gallery hoặc sau khi chụp:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e4YJtuZlNo7x2lNlX2dIUQ.png" /></figure><p>dữ liệu sẽ được trả về trong biến <strong>data: Intent, convert </strong>sang<strong> Bitmap</strong> và lấy<strong> Bitmap convert </strong>sang<strong> Base64 (String)</strong></p><p>====&gt; chuyển <strong>ảnh</strong> sang <strong>base64 String:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wSUdLu-X-oEO30OocdVnfA.png" /></figure><p>====&gt; Gữi dữ liệu lên <strong>server</strong> để dự đoán <strong>nhãn</strong> của <strong>ảnh</strong></p><p>Sử dụng thư viện <strong>Retrofit</strong> để gởi dữ liệu<strong> Base64 String</strong> lên <strong>server</strong> thông qua phương thức <strong>POST</strong> với <strong>endpoint /api/post_some_data</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o1IZn1r4NfwFwjYAtghqYg.png" /></figure><p><strong>*getPredictLabelPost: với đối số truyền vào là 1 body </strong>bao gồm trường <strong>json </strong>để<strong> </strong>phía<strong> client </strong>và<strong> server</strong> dùng để gởi và nhận kết quả:</p><blockquote>{<br> <strong>image</strong>: ”bdhasbdh7VDGVCSVCD271tdvVDGHVS”<br>}</blockquote><p>**<strong>Client</strong> sẽ gởi 1 <strong>json</strong> với <strong>key</strong> là <strong>image kèm value </strong>là<strong> 1 base64 </strong>của<strong> ảnh</strong></p><p>**<strong>Server </strong>nhận kết quả từ<strong> key</strong> là trường<strong> image -&gt; convert base64 </strong>sang ảnh để nhận dạng.</p><p><strong>GỞI request và nhận kết quả trả về:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*peQtjlvvVLy1Ut2Zvq8bgg.png" /></figure><p>Lưu ý: hàm <strong>excute() </strong>cần chạy ở <strong>background thread.</strong></p><p>Kết quả sau khi nhận dạng ảnh sẽ là biến <strong>label.</strong></p><h3>Tham khảo:</h3><ul><li><a href="https://www.csie.ntu.edu.tw/~cjlin/libsvm/">LIBSVM -- A Library for Support Vector Machines</a></li><li><a href="https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_sift_intro/py_sift_intro.html">Introduction to SIFT (Scale-Invariant Feature Transform) - OpenCV-Python Tutorials 1 documentation</a></li><li><a href="https://medium.freecodecamp.org/an-introduction-to-bag-of-words-and-how-to-code-it-in-python-for-nlp-282e87a9da04">An introduction to Bag of Words and how to code it in Python for NLP</a></li><li><a href="http://flask.pocoo.org/">Welcome | Flask (A Python Microframework)</a></li><li><a href="https://stackoverflow.com/questions/30180957/send-post-request-with-params-using-retrofit">Send Post Request with params using Retrofit</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=99e3fce5f2a6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Panorama Pictures created?]]></title>
            <link>https://medium.com/@huynhquoc/%E1%BA%A3nh-panorama-%C4%91%C6%B0%E1%BB%A3c-t%E1%BA%A1o-ra-nh%C6%B0-th%E1%BA%BF-n%C3%A0o-1c509bbaae39?source=rss-589af7f792d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/1c509bbaae39</guid>
            <category><![CDATA[360-panorama-photo]]></category>
            <category><![CDATA[panorama]]></category>
            <category><![CDATA[image-processing]]></category>
            <category><![CDATA[information-technology]]></category>
            <category><![CDATA[computer-vision]]></category>
            <dc:creator><![CDATA[Quốc Huỳnh]]></dc:creator>
            <pubDate>Sat, 22 Dec 2018 10:28:11 GMT</pubDate>
            <atom:updated>2018-12-25T06:30:24.614Z</atom:updated>
            <content:encoded><![CDATA[<h3>Giới thiệu sơ</h3><p>Panorama là một thành công trong lĩnh vực thị giác máy tính. Được xuất bản bởi <strong>Matthew Brown and David G. Lowe</strong> trong cuốn <a href="http://matthewalunbrown.com/papers/ijcv2007.pdf">panoramic image stitching paper in 2007</a>. Kể từ đó nó được áp dụng trong nhiều kể từ nay như <strong>GoogleStreetView </strong>hoặc điển hình như các ứng dụng chụp ảnh ở các điện thoại cảm ứng hiện nay.</p><p>Trong bài này mình sẽ giới thiệu cách mà mình hiểu về một ảnh Panorama được tạo bởi những cách thức, giải thuật nào <strong>(chỉ là hiểu biết cơ bản của bản thân)</strong>.</p><p>Cần hiểu một số giải thuật/ khái niệm trong Thị giác máy tính như: Phát hiện góc (giải thuật <strong>Harris corner detector</strong>), Bộ mô tả của ảnh (<strong>Descriptor of keypoints</strong>).</p><p>** Mô tả về quy trình mà ghép 2 ảnh chụp gần giống nhau để cho ra một ảnh Panorama: Đầu tiên ta tiến hành sử dụng giải thuật Harris Corner Detector để tiến hành tìm các điểm chính trong từng ảnh (các điểm ảnh này là <strong>góc</strong>), Sau khi trên mỗi ảnh ta đã có các <strong>keypoints,</strong> sau khi có tập các điểm <strong>keypoints</strong> trên mỗi ảnh ta tiến hành xây dựng một bộ mô tả cho mỗi ảnh bởi mỗi tập <strong>keypoints</strong> của mỗi ảnh (bộ mô tả kiểu sẽ nói lên 2 ảnh gần giống nhau sẽ có bộ mô tả gần giống nhau tại mỗi điểm <strong>keypoint</strong>). Sau khi có 2 bộ mô tả ta tiến hành so khớp 2 ảnh với nhau(<strong>keypoint ở ảnh 1 sẽ là keypoint nào ở ảnh 2</strong>). Sau bước này ta có được các cặp<strong> keypoint matched</strong> với nhau cuối cùng áp dụng giải thuật ước lượng chuyển đổi của Ma trận (<strong>Transformation Estimation</strong>) để tiến hành tìm 1 ma trận mà biến đổi ảnh 1 và ảnh 2 và sau đó ghép ảnh lại và cuối cùng là tối ưu để cho ra 1 ảnh Panorama hoàn thiện.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/956/1*j0NYYfC-b7i5KjGOhFqnPg.png" /><figcaption>Hai tấm ảnh được chụp lệch nhau</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/944/1*6S66vd0GKGyOZSAcRUSz5w.png" /><figcaption>Mô tả các keypoints trên từng ảnh</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XDgBmtJF7JMFlxtbU5ujYw.png" /><figcaption>Ghép ảnh sau khi tìm được ma trận chuyển đổi</figcaption></figure><p>Một ảnh Panorama sẽ trải qua các bước như sau:</p><p>1/ Phát góc trong ảnh (<strong>Harris corner detector</strong>).</p><p>2/ Xây dựng một bộ mô tả cho ảnh.</p><p>3/ So khớp giữa 2 hay nhiều ảnh với nhau.</p><p>4/ Ghép ảnh.</p><p>5/ Tối ưu ảnh hoàn chỉnh <strong>panorama (Blending)</strong></p><h3><strong>1/ Phát góc trong ảnh (Harris corner detector).</strong></h3><p>Ở bước này nôm na là ta sẽ đi tìm các đặc trưng cục bộ trong ảnh, mà đặc trưng này là các điểm ảnh như một góc cạnh.</p><p><strong>Vậy làm sao để biết được pixel trên ảnh đó nó là góc?</strong></p><p>Để nói pixel đó là góc ta tiến hành:</p><p>Bước 1: Đạo hàm theo theo Gx,Gy trên ảnh đầu vào</p><p>Bước 2: Tính các tổng bình phương của các kết quả đạo hàm ở <strong>Bước 1</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/183/0*0gzcNxV-m_4brxzX" /></figure><p>Bước 3: Xây dựng ma trận M và tính giá trị R:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/221/0*W7p7H4s9k_1xacQm" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/211/0*0mKjoLepxyVKSht4" /><figcaption><strong>det </strong>là tính định thức của ma trậ M,<strong> trace </strong>là giá trị đường chéo chính của ma trận M</figcaption></figure><p>Khi đã có giá trị R ta xác định nó có phải là góc hay không:</p><ul><li>nếu R có giá trị nhỏ thì nó là vùng pixel không có giá trị hay còn gọi là vùng <strong>“phẳng&quot;.</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/100/1*NkDRz3BECaQyQmbfcbq7pw.png" /><figcaption>Hình 4: vùng pixel phẳng</figcaption></figure><ul><li>nếu R &lt; 0 thì vùng pixel chỉ chứa <strong>“cung&quot;.</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/100/1*VhTeA4Pl4ior_D3Xi9M2Hg.png" /><figcaption>Hình 5: vùng pixel chứa cung</figcaption></figure><ul><li>nếu R lớn thì xác định nó là <strong>“góc&quot;.</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/100/1*6HMbBiagVBZ_n_OATxBJkg.png" /><figcaption>Hình 6: vùng pixel chứa góc</figcaption></figure><p>Kết quả ở bước trên ta sẽ có một ảnh như sau:</p><p><a href="https://github.com/quocb14005xx/ThiGiacMayTinhCTU/blob/master/ThiGiacMayTinh_Homework/hw3/panorama.py"><em>Code để thực hiện tìm góc</em>(hàm <strong>harris_corners()</strong>)</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*-sGjbC-a6dd5jaKCRF-sqg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RBs7z2CWkZ5O12aTFROwew.png" /></figure><h3>2/ Xây dựng một bộ mô tả cho ảnh</h3><p>Sau khi đã tìm ra các <strong>keypoints</strong> của ảnh, trên từng <strong>keypoint </strong>ta tiến hành thực hiện mô tả nó.</p><p>Có nhiều cách để mô tả một điểm <strong>keypoint</strong> cho bài toán này. Cách đơn giản nhất là ứng với mỗi điểm <strong>keypoint </strong>ta tiến hành khởi tạo 1 miếng vá điểm ảnh này <strong>(không biết gọi là gì) </strong>nôm na như ô vuông xanh xanh <strong>hình 4,5,6.</strong></p><p>Với mỗi ô vuông xanh (miếng vá) như <strong>hình 4 5 6, </strong>nó là một ma trận 2 chiều (<strong>M</strong> hàng <strong>N</strong> cột)ta tiến hành trải nó thành 1 <strong>vector </strong>với số <strong>chiều</strong> là<strong> MxN chiều</strong></p><p>Ví dụ một ma trận M =3 hàng, N = 3 cột:</p><p><strong>1 2 3</strong><br><strong>4 5 6 </strong>=&gt; <strong>[1 2 3 4 5 6 7 8 9]</strong><br><strong>7 8 9</strong></p><p>Vì thế cứ mỗi <strong>keypoint</strong> ta sẽ có 1 <strong>vector, </strong>với<strong> N keypoint </strong>ta sẽ có<strong> N vector. ===&gt; Vậy một bộ mô tả của một ảnh sẽ là N vector như sau:</strong></p><p>[ X1 X2 X3 X4 ………. X<strong><em>số chiều</em></strong>]<strong> =&gt; vector keypoint 1</strong><br>[ X1 X2 X3 X4 ………. X<strong><em>số chiều</em></strong>] =&gt; <strong>vector keypoint 2</strong><br>.<br>.<br>.<br>[ X1 X2 X3 X4 ………. X<strong><em>số chiều</em></strong>] =&gt; <strong>vector keypoint thứ N</strong></p><p><a href="https://github.com/quocb14005xx/ThiGiacMayTinhCTU/blob/master/ThiGiacMayTinh_Homework/hw3/panorama.py">CODE để xây dựng bộ mô tả (sử dụng hàm <strong>simple_descriptor()</strong> cho cách mô tả đơn giản, và hàm <strong>describe_keypoints()</strong>)</a></p><h3>3/ So khớp giữa 2 hay nhiều ảnh với nhau.</h3><p>Sau khi có bộ mô tả trên từng ảnh như trên, ta tiến hành đi so khớp giữa 2 hay nhiều bộ mô tả đó với nhau bằng các sau:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kcQ8bPy6MDCV8un5TfCIVw.png" /></figure><p>Ví dụ ở 2 ảnh: tức là ta có 2 bộ mô tả. <strong>Vậy điểm nào trên ảnh sẽ khớp với điểm nào?</strong></p><p>Ở phương pháp này ta tiến hành như sau:</p><p>Với một <strong>vector</strong> bên <strong>ảnh 1</strong> ta đi tìm<strong> </strong><a href="https://vi.wikipedia.org/wiki/Kho%E1%BA%A3ng_c%C3%A1ch_Euclid"><strong>khoảng cách Euclid</strong></a><strong> v</strong>ới tất cả các<strong> vector </strong>bên <strong>ảnh 2, </strong>nó là<strong> Matched (khớp nhau)</strong> với điều kiện là khoảng cách <strong>gần nhất nhỏ hơn </strong>khoảng cách<strong> gần nhất thứ hai </strong>với một tỉ lệ<strong> r </strong>nhỏ hơn ngưỡng<strong> threshold </strong>(khó hiểu như …..). OK, mình sẽ mô hình hoá nó như sau:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*66lgSyknRk-kQCw7SiCCSQ.png" /></figure><p>Ví dụ khoảng các giữa <strong>vector1</strong> ảnh <strong>A</strong> với các <strong>vector</strong> trong ảnh <strong>B lần lượt là distance1–1, distance1–2, distance1–3, distance1–4, </strong>ví dụ trong các khoảng cách trên khoảng các giữa<strong> vector1 </strong>với<strong> vector3, vector1 </strong>với<strong> vector4 </strong>là <strong>nhỏ nhất </strong>để nói<strong> keypoint ảnh A </strong>có mô tả là<strong> vector1</strong> khớp với<strong> keypoint </strong>có mô tả là <strong>vector3 </strong>trong<strong> ảnh B </strong>thì<strong> tỉ lệ </strong>giữa<strong> distance1–3</strong> và <strong>distance1–4 </strong>phả<strong>i nhỏ hơn 1 ngưỡng </strong>xét thì <strong>2 keypoints </strong>đó là khớp với nhau<strong>, </strong>tương tự các<strong> keypoints </strong>khác.</p><p><strong>Sau bước này ta thu được các cặp keypoints khớp nhau.</strong></p><p><a href="https://github.com/quocb14005xx/ThiGiacMayTinhCTU/blob/master/ThiGiacMayTinh_Homework/hw3/panorama.py">Code để thực hiện tính các keypoints khớp nhau(hàm <strong>matching_keypoints()</strong>)</a></p><h3>4/ Ghép ảnh.</h3><p>Ghép ảnh bằng phương pháp ước lượng ma trận chuyển đổi (<strong>Transformation Estimation</strong>)</p><p>Cụ thể như sau: sau <strong>Bước 3</strong> ta có tập các điểm khớp giữa 2 ảnh: Keypoint <strong>pA </strong>ảnh A tại vị trí pixel thứ <strong>(Xa,Ya)</strong> và Keypoint <strong>pB </strong>ảnh B tại vị trí pixel <strong>(Xb,Yb) </strong>hớp nhau ta tiến hành tìm một ma trận biến đổi <strong>H </strong>sao cho<strong> pA.H = pB (</strong>đơn giản chỉ áp dụng công thức <a href="https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.linalg.lstsq.html"><strong>least-squares solution to a linear matrix equation</strong></a><strong> </strong>để tính<strong> H)</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0jDrO-nxkviPmDEQisFFmw.png" /><figcaption>Image 2 đã biển đổi với ma trận <strong>H</strong></figcaption></figure><p>==&gt; CUỐI CÙNG chỉ cần <strong>wrap</strong> 2 ảnh này lại bằng cách nào đó cho ngôn ngữ mà bạn lập trình.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cjRmeESDQcMM9M9w0EQ-OQ.png" /><figcaption><strong>Wrap 2 ảnh</strong></figcaption></figure><p>Ảnh sau khi ghép lại còn một số chưa hoàn chỉnh, để có một ảnh hoàn chỉnh hơn tiến hành sang <strong>Bước 5 Blending</strong></p><h3>Bước 5: Blending</h3><p><a href="https://courses.engr.illinois.edu/cs498dwh/fa2010/lectures/Lecture%2017%20-%20Photo%20Stitching.pdf">Tham khảo ở đây để thực hiện vì bước này không có gì để nói</a>, <a href="https://github.com/quocb14005xx/ThiGiacMayTinhCTU/blob/master/ThiGiacMayTinh_Homework/hw3/panorama.py"><strong>apply my code now</strong></a></p><p>Taì liệu tham khảo:</p><p><a href="https://courses.engr.illinois.edu/cs498dwh/fa2010/lectures/Lecture%2017%20-%20Photo%20Stitching.pdf">https://courses.engr.illinois.edu/cs498dwh/fa2010/lectures/Lecture%2017%20-%20Photo%20Stitching.pdf</a></p><p><a href="http://cui.unige.ch/~benbelga/examenThesis_MohamedBENBELGACEM.pdf">http://cui.unige.ch/~benbelga/examenThesis_MohamedBENBELGACEM.pdf</a></p><p>Slide bài giảng Thị giác máy tính, PGS TS Phạm Nguyên Khang</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1c509bbaae39" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>