<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>FS K&apos;s / LOG</title><link>https://fskuan.com/</link><description>從藍牙封包、Web 工具到基礎設施，記錄問題如何被定位、拆解與驗證。</description><lastBuildDate>Sat, 13 Jul 2024 08:24:50 GMT</lastBuildDate><item><title>Next.js Experimental https Error on Windows</title><link>https://fskuan.com/posts/next.js/next.js-experimental-https-error/</link><guid isPermaLink="true">https://fskuan.com/posts/next.js/next.js-experimental-https-error/</guid><pubDate>Sat, 13 Jul 2024 08:24:50 GMT</pubDate><description>Next.js Experimental https Error on Windows</description><content:encoded><![CDATA[<h2 id="error"><a href="#error"><span class="icon icon-link"></span></a>Error</h2>
<ul>
<li>執行 npm run dev 時，發生以下錯誤</li>
</ul>
<pre><code>&gt; projectname@0.0.1 dev
&gt; next dev -p 3000 --experimental-https

 ⚠ Self-signed certificates are currently an experimental feature, use at your own risk.
   Attempting to generate self signed certificate. This may prompt for your password
 ⨯ Failed to generate self-signed certificate. Falling back to http. Error: Command failed: "mkcert-v1.4.4-windows-amd64.exe" -install -key-file "projectname\certificates\localhost-key.pem" -cert-file "projectname\certificates\localhost.pem" localhost 127.0.0.1 ::1
    at checkExecSyncError (node:child_process:890:11)
    at execSync (node:child_process:962:15)
    at createSelfSignedCertificate (projectname\node_modules\next\dist\lib\mkcert.js:122:37)
    at async runDevServer (projectname\node_modules\next\dist\cli\next-dev.js:293:35)
    at async nextDev (projectname\node_modules\next\dist\cli\next-dev.js:308:5)
    at async main (projectname\node_modules\next\dist\bin\next:155:5) {
  status: 1,
  signal: null,
  output: [ null, null, null ],
  pid: 2436,
  stdout: null,
  stderr: null
}
   ▲ Next.js 14.1.4
   - Local:        http://localhost:3000

 ✓ Ready in 2.3s
</code></pre>
<h2 id="solution"><a href="#solution"><span class="icon icon-link"></span></a>Solution</h2>
<ul>
<li>以 "系統管理員" 開啟 Powershell 至專案目錄，再次執行 npm run dev</li>
<li>僅需處理一次，之後可使用 IDE 執行專案</li>
</ul>]]></content:encoded></item><item><title>Build From Source - mail, http2, http3, stream modules</title><link>https://fskuan.com/posts/nginx/build-from-source/</link><guid isPermaLink="true">https://fskuan.com/posts/nginx/build-from-source/</guid><pubDate>Fri, 26 Apr 2024 08:24:50 GMT</pubDate><description>Build From Source with mail, http2, http3, stream modules</description><content:encoded><![CDATA[<h2 id="motivation"><a href="#motivation"><span class="icon icon-link"></span></a>Motivation</h2>
<ul>
<li>想要自己編譯 Nginx，並且加入一些模組</li>
<li>新開了一個 Mail Server，想要使用 Nginx 作為 Mail Proxy Server</li>
</ul>
<h2 id="evnironment"><a href="#evnironment"><span class="icon icon-link"></span></a>Evnironment</h2>
<ul>
<li>Ubuntu 22.04</li>
</ul>
<h2 id="procedure"><a href="#procedure"><span class="icon icon-link"></span></a>Procedure</h2>
<ol>
<li>
<p>安裝編譯 Nginx 所需的套件</p>
<pre><code class="language-bash">sudo apt update
sudo apt install -y build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev
</code></pre>
</li>
<li>
<p>安裝 PCRE (依照自身需求修改版本號)</p>
<pre><code>wget github.com/PCRE2Project/pcre2/releases/download/pcre2-10.42/pcre2-10.42.tar.gz
tar -zxf pcre2-10.42.tar.gz
cd pcre2-10.42
./configure
make
sudo make install
</code></pre>
</li>
<li>
<p>安裝 zlib (依照自身需求修改版本號)</p>
<pre><code>wget http://zlib.net/zlib-1.2.13.tar.gz
tar -zxf zlib-1.2.13.tar.gz
cd zlib-1.2.13
./configure
make
sudo make install
</code></pre>
</li>
<li>
<p>安裝 nginx (依照自身需求修改版本號)</p>
<pre><code>wget https://nginx.org/download/nginx-1.24.0.tar.gz
tar zxf nginx-1.24.0.tar.gz
cd nginx-1.24.0
</code></pre>
</li>
<li>
<p>設定模組 (以下指令必須為單行)</p>
<pre><code>./configure \
    --with-pcre=../pcre2-10.42 \
    --with-zlib=../zlib-1.3.1 \
    --with-http_ssl_module \
    --with-mail \
    --with-mail_ssl_module \
    --with-stream \
    --with-stream_ssl_module \
    --with-http_v2_module \
    --with-http_v3_module
</code></pre>
</li>
<li>
<p>編譯並安裝</p>
<pre><code>make
sudo make install
</code></pre>
</li>
<li>
<p>安裝完成的 Nginx 位於 <code>/usr/local/nginx/</code>，設定檔位於 <code>/usr/local/nginx/conf/nginx.conf</code></p>
</li>
<li>
<p>Nginx 執行檔位於 <code>/usr/local/nginx/sbin/nginx</code>，啟動 Nginx</p>
<pre><code>sudo /usr/local/nginx/sbin/nginx
</code></pre>
</li>
<li>
<p>把 Nginx 執行檔加入系統路徑</p>
<pre><code>sudo ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/nginx
</code></pre>
</li>
<li>
<p>設定 Nginx 服務</p>
<pre><code>sudo vim /etc/systemd/system/nginx.service
</code></pre>
</li>
<li>
<p>nginx.service 內容</p>
<pre><code>[Unit]
Description=Nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s quit
ExecStartPost=/bin/sleep 0.1
PIDFile=/usr/local/nginx/logs/nginx.pid

[Install]
WantedBy=multi-user.target
</code></pre>
</li>
<li>
<p>Reload systemd</p>
<pre><code>sudo systemctl daemon-reload
</code></pre>
</li>
<li>
<p>啟動 Nginx</p>
<pre><code>sudo systemctl start nginx
</code></pre>
</li>
<li>
<p>查看 Nginx 狀態</p>
<pre><code>sudo systemctl status nginx
</code></pre>
</li>
</ol>
<h2 id="reference"><a href="#reference"><span class="icon icon-link"></span></a>Reference</h2>
<ul>
<li><a href="https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#installing-nginx-dependencies">Installing NGINX Open Source</a></li>
<li><a href="https://docs.nginx.com/nginx/admin-guide/mail-proxy/mail-proxy/">Configuring NGINX as a Mail Proxy Server</a></li>
</ul>]]></content:encoded></item><item><title>Monitoring with Prometheus, Blackbox Exporter and alertmanager</title><link>https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/</link><guid isPermaLink="true">https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/</guid><pubDate>Mon, 26 Feb 2024 05:16:57 GMT</pubDate><description>Monitoring with Prometheus, Blackbox Exporter and alertmanager</description><content:encoded><![CDATA[<h2 id="suitable-for"><a href="#suitable-for"><span class="icon icon-link"></span></a>Suitable For</h2>
<ul>
<li>快速建立監控系統</li>
<li>Prometheus, Blackbox Exporter, Alertmanager 簡單概念</li>
<li>Prometheus, Blackbox Exporter, Alertmanager 的設定</li>
<li>結合 Slack 通知</li>
</ul>
<h2 id="concepts"><a href="#concepts"><span class="icon icon-link"></span></a>Concepts</h2>
<h3 id="prometheus-architecture-overview1"><a href="#prometheus-architecture-overview1"><span class="icon icon-link"></span></a>Prometheus Architecture Overview[1]</h3>
<p>先上 Prometheus 的架構圖[2] ( 來源 Prometheus Github[2] )</p>
<p><picture><img alt="Prometheus Architecture Overview" class="article-image article-wide" decoding="async" height="481" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/architecture.svg" width="699" /></picture></p>
<p>本次主要使用到的部分有 Prometheus, Blackbox Exporter, Alertmanager</p>
<p>主要的功能如下：</p>
<ul>
<li>Prometheus: 監控系統</li>
<li>Blackbox Exporter: 以 Blackbox 的方式，監控外部服務，此次主要用來監控網站是否存活</li>
<li>Alertmanager: 通知系統接受 Prometheus 的警告訊息，並進行通知，可以透過 Slack, Email 等方式通知</li>
</ul>
<h2 id="evnironment"><a href="#evnironment"><span class="icon icon-link"></span></a>Evnironment</h2>
<ul>
<li>支援 docker, docker compose 的主機 ( 例如 Ubuntu 22.04.2 )</li>
<li>欲監控的網站 ( 例如 fskuan.com )</li>
<li>Slack 帳號</li>
</ul>
<h2 id="procedure"><a href="#procedure"><span class="icon icon-link"></span></a>Procedure</h2>
<ol>
<li>
<p>Docker Compose File 如下，相關的設定檔同步放在 <a href="https://github.com/ghit42796/PrometheusMonitoringSystemDockerCompose">Github</a> 上</p>
<pre><code>version: '3.7'

services:
    prometheus:
        image: prom/prometheus:main
        volumes:
        - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
        - ./config/alerts.yml:/etc/prometheus/alerts.yml
        networks:
        - monitoring_network
        ports:
        - "9090:9090"
        command:
        - "--config.file=/etc/prometheus/prometheus.yml"
        - "--web.enable-lifecycle"

    blackbox_exporter:
        image: prom/blackbox-exporter:master
        volumes:
        - ./config/blackbox.yml:/etc/blackbox_exporter/config.yml
        networks:
        - monitoring_network
        ports:
        - "9115:9115"

    alertmanager:
        image: prom/alertmanager:main
        volumes:
        - ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
        networks:
        - monitoring_network
        ports:
        - "9093:9093"

networks:
    monitoring_network:
        driver: bridge
        ipam:
        config:
            - subnet: 172.28.0.0/16
</code></pre>
<p>Docker Compose 中只有簡單的 image, volumes, networks, ports 的設定，不多做贅述</p>
</li>
<li>
<p>其他的設定都放在 config 資料夾底下，先介紹 Blackbox Exporter 的設定檔 ( blackbox.yml )</p>
<pre><code>modules:
    http_2xx_ip4:                           # 新增一個監控的模組
        prober: http                        # 使用 http 的方式監控
        timeout: 10s                        # 超時時間
        http:                               # http 的設定
            preferred_ip_protocol: "ip4"    # 使用 IPv4
            method: GET                     # 使用 GET 方法
</code></pre>
<p>Blackbox 會優先使用 IPv6，由於我的環境中一律使用 IPv4，所以特別指定使用 IPv4，以避免無法正確監控的問題</p>
<p>具體說明可以參考此 <a href="https://www.robustperception.io/checking-for-http-200s-with-the-blackbox-exporter/">Blog</a></p>
</li>
<li>
<p>接下來設定 Prometheus Config</p>
<pre><code>global:
    scrape_interval: 20s                            # 全域設定抓取間隔，每20秒抓取一次監控數據

alerting:
    alertmanagers:
        - static_configs:
            - targets:
                - "alertmanager:9093"               # 指定 Alertmanager 的地址，Prometheus 將警報發送到這個地址

rule_files:
    - "alerts.yml"                                  # 指定警報規則文件，Prometheus 會根據這里定義的規則生成警報

scrape_configs:
    - job_name: 'prometheus'
      static_configs:
        - targets: ['localhost:9090']

    - job_name: 'blackbox'                          # 設定 blackbox 任務
        metrics_path: /probe
        params:
            module: [http_2xx_ip4]                  # 使用剛才客製化的模組
        static_configs:
            - targets:                              # 設定要監控的網站列表
                - https://fskuan.com
                - https://example1.fskuan.com
                - https://example2.fskuan.com/swagger/index.html
        relabel_configs:                            # 使用重標簽配置來修改監控目標的標簽和地址
            - source_labels: [__address__]
            target_label: __param_target            # 將目標地址設置為 '__param_target' 參數，供 Blackbox Exporter 使用
            - source_labels: [__param_target]
            target_label: instance                  # 將 '__param_target' 參數的值覆制到 'instance' 標簽，用於標識實例
            - target_label: __address__
            replacement: blackbox_exporter:9115     # 將 '__address__' 標簽的值替換為 Blackbox Exporter 的地址，這樣 Prometheus 就會抓取 Blackbox Exporter 提供的監控數據
</code></pre>
</li>
<li>
<p>設定警報規則 ( alert.yml )</p>
<pre><code>groups:
    - name: 'WebService'
      rules:
            - alert: WebSiteDown
              expr: probe_success{job="blackbox"} == 0  # probe sucess = 0 意思是目標網站抓取失敗
              for: 1m                                   # 持續失敗 1 分鐘
              labels:
                    severity: critical
              annotations:                              # 設定的訊息內容，會顯示在對應的通知中，例如 Slack
                    summary: "Web site down (instance {{ $labels.instance }})"
                    description: "The web site {{ $labels.instance }} has been down for more than 1 minute."
</code></pre>
</li>
<li>
<p>設定警報管理規則 ( alertmanager.yml )</p>
<pre><code>global:
    resolve_timeout: 5m

route:
    group_by: ['alertname']
    group_wait: 10s
    group_interval: 10s
    repeat_interval: 10m
    receiver: 'slack'

receivers:
    - name: 'slack'
      slack_configs:
        - api_url: '{your_slack_api_url_here}'          # 設定 Slack 應用程式 API
          channel: '#monitoring'
          text: "{{ range .Alerts }} {{ .Annotations.description}}\n {{end}} {{ .CommonAnnotations.username}} &lt;{{.CommonAnnotations.link}}| click here&gt;"
          title: "{{.CommonAnnotations.summary}}"
          title_link: "{{.CommonAnnotations.link}}"
          color: "{{.CommonAnnotations.color}}"
</code></pre>
</li>
<li>
<p>啟動 docker compose</p>
<pre><code>docker compose up -d
</code></pre>
</li>
<li>
<p>可以於 host_ip:9090 看到 Prometheus UI
<picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.480.avif 480w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.680.avif 680w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.960.avif 960w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.1440.avif 1440w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.1910.avif 1910w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.480.webp 480w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.680.webp 680w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.960.webp 960w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.1440.webp 1440w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.1910.webp 1910w" type="image/webp"></source><img alt="Prometheus query interface before an expression has been entered" class="article-image article-wide" decoding="async" height="418" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_ui.png" width="1910" /></picture></p>
</li>
<li>
<p>如果 alerts.yml 有正確設定的話，可以於 Alert 分頁看到設定結果
<picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_alert.480.avif 480w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_alert.680.avif 680w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_alert.770.avif 770w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_alert.480.webp 480w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_alert.680.webp 680w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_alert.770.webp 770w" type="image/webp"></source><img alt="Prometheus Alerts page showing an inactive WebSiteDown rule based on the probe_success metric" class="article-image article-wide" decoding="async" height="408" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_alert.png" width="770" /></picture></p>
</li>
<li>
<p>於 Graph 可以透過 PromQL ( Prometheus Query Language ) 查詢結果
<picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.480.avif 480w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.680.avif 680w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.960.avif 960w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.1440.avif 1440w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.1889.avif 1889w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.480.webp 480w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.680.webp 680w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.960.webp 960w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.1440.webp 1440w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.1889.webp 1889w" type="image/webp"></source><img alt="Prometheus graph for probe_success showing a steady value of one over an hour" class="article-image article-wide" decoding="async" height="755" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/prometheus_graph.png" width="1889" /></picture></p>
</li>
<li>
<p>於 host_ip:9115, host_ip:9093 看到 blackbox_exporter, alertmanager 提供的簡單 UI 內容
<picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/blackbox_exporter.480.avif 480w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/blackbox_exporter.523.avif 523w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/blackbox_exporter.480.webp 480w, https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/blackbox_exporter.523.webp 523w" type="image/webp"></source><img alt="Blackbox Exporter web page listing recent HTTP probe targets with successful results" class="article-image" decoding="async" height="559" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/monitoring/monitoring_with_prometheus_blackbox_exporter_and_alertmanager/blackbox_exporter.png" width="523" /></picture></p>
</li>
</ol>
<h2 id="reference"><a href="#reference"><span class="icon icon-link"></span></a>Reference:</h2>
<ul>
<li>[1] <a href="https://github.com/prometheus/prometheus/blob/main/documentation/images/architecture.svg">Prometheus Architecture Overview</a></li>
<li>[2] <a href="https://github.com/prometheus/prometheus">Prometheus Github</a></li>
<li>[3] <a href="https://github.com/ghit42796/PrometheusMonitoringSystemDockerCompose">Github Repo</a></li>
</ul>]]></content:encoded></item><item><title>Get data from BLE with Python Bleak</title><link>https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/</link><guid isPermaLink="true">https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/</guid><pubDate>Fri, 26 Jan 2024 02:29:36 GMT</pubDate><description>How to get data from BLE devices with Python Bleak</description><content:encoded><![CDATA[<h2 id="suitable-for"><a href="#suitable-for"><span class="icon icon-link"></span></a>Suitable For</h2>
<ul>
<li>有 Python 基本概念 或 程式語言基礎</li>
</ul>
<h2 id="prerequisite"><a href="#prerequisite"><span class="icon icon-link"></span></a>Prerequisite</h2>
<ul>
<li>Software
<ul>
<li>Python 3.8 or above ( 2024/01/26: Bleak 0.21.1 only support Python 3.8 or above )</li>
</ul>
</li>
<li>Hardware
<ul>
<li>BLE device</li>
<li>Computer with BLE support ( 支援 Bluetooth 4.0 以上的電腦 )</li>
</ul>
</li>
</ul>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<p>本篇利用 Python 的 Bleak 套件來取得資料，並直接解析資料</p>
<p>Bleak 是 MIT 授權的開源套件，其相關的文件放在 Reference 1-3</p>
<p>本篇所有程式碼都放在 <a href="https://github.com/ghit42796/Get-Data-from-BLE-with-Python-Bleak">Github</a></p>
<ol>
<li>
<p>安裝 Bleak</p>
<pre><code class="language-bash">pip install bleak
</code></pre>
</li>
<li>
<p>import Bleak 並且掃描周遭裝置</p>
<pre><code>import asyncio
from bleak import BleakScanner, BleakClient

async def main():
    # Scan for all available devices
    devices = await BleakScanner.discover()

    for device in devices:
        print(device)

asyncio.run(main())
</code></pre>
<p>執行結果如下圖:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/1.236.avif 236w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/1.236.webp 236w" type="image/webp"></source><img alt="Terminal output listing discovered Bluetooth device addresses with unavailable names" class="article-image" decoding="async" height="302" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/1.png" width="236" /></picture></p>
<p>會找到所有周遭的裝置，並且列出 裝置地址 與 裝置名稱，後續可以利用這兩個資訊來建立連線</p>
</li>
<li>
<p>透過指定 裝置地址 或 裝置名稱 來建立連線</p>
<ul>
<li>
<p>指定裝置名稱</p>
<pre><code>import asyncio
from bleak import BleakScanner

target_device_name = "YOUR_DEVICE_NAME"

async def main():
    # Scan for all available devices
    devices = await BleakScanner.discover()

    for device in devices:
        if device.name == target_device_name:
            print(f"Found target device: {device.name}, {device.address}")

asyncio.run(main())
</code></pre>
</li>
<li>
<p>指定裝置地址</p>
<pre><code>import asyncio
from bleak import BleakScanner

target_address = "YOUR_DEVICE_ADDRESS"

async def main():
    # Scan for all available devices
    devices = await BleakScanner.discover()

    for device in devices:
        if device.address == target_address:
            print(f"Found target device: {device.name}, {device.address}")

asyncio.run(main())
</code></pre>
</li>
</ul>
</li>
<li>
<p>建立連線後，表列出此裝置所支援的所有 Services 和 Characteristics</p>
<pre><code>import asyncio
from bleak import BleakScanner, BleakClient

target_device_name = "YOUR_DEVICE_NAME"

async def main():
    # Scan for all available devices
    devices = await BleakScanner.discover()

    for device in devices:
        if device.name == target_device_name:
            print(f"Found target device: {device.name}, {device.address}")

            # Connect to the target device
            async with BleakClient(device) as client:
                print(f"Connected: {client.is_connected}")

                services = await client.get_services()
                # Print all services and characteristics
                for service in services:
                    print(f"Service: {service.uuid}")
                    for char in service.characteristics:
                        print(f"\tCharacteristic: {char.uuid}")

asyncio.run(main())
</code></pre>
<p>執行結果如下圖:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/2.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/2.553.avif 553w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/2.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/2.553.webp 553w" type="image/webp"></source><img alt="Terminal output listing Generic Access, Generic Attribute, and Device Information service UUIDs and characteristics" class="article-image" decoding="async" height="303" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/2.png" width="553" /></picture></p>
<p>上圖表示的是此裝置所支援的所有 Services 和 Characteristics，僅有列出 UUID，此 UUID 是<a href="https://fskuan.com/posts/bluetooth/get_data_from_ble/">上一篇文章</a>中提到的 ATT 中的 Attribute Types，代表服務類型</p>
<p>具體的服務類型需要參考 藍牙 或 裝置 規格書</p>
<ul>
<li><a href="https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Assigned_Numbers/out/en/Assigned_Numbers.pdf">藍牙規格書</a></li>
</ul>
<p>依照藍牙規格書可以對應出上圖中的服務類型:</p>
<table>
<thead>
<tr>
<th>類型</th>
<th>UUID</th>
<th>名稱</th>
</tr>
</thead>
<tbody>
<tr>
<td>Service</td>
<td>0x1800</td>
<td>Generic Access service</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2A00</td>
<td>Device Name</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2A01</td>
<td>Appearance</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2A04</td>
<td>Peripheral Preferred Connection Parameters</td>
</tr>
<tr>
<td>Service</td>
<td>0x1801</td>
<td>Generic Attribute service</td>
</tr>
<tr>
<td>Service</td>
<td>0x180A</td>
<td>Device Information service</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a23</td>
<td>System ID</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a24</td>
<td>Model Number String</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a25</td>
<td>Serial Number String</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a26</td>
<td>Firmware Revision String</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a27</td>
<td>Hardware Revision String</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a28</td>
<td>Software Revision String</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a29</td>
<td>Manufacturer Name String</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a2a</td>
<td>IEEE 11073-20601 Regulatory Certification Data List</td>
</tr>
<tr>
<td>Characteristic</td>
<td>0x2a50</td>
<td>PnP ID</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>透過指定 Service 和 Characteristic 來取得資料</p>
<pre><code>import asyncio
from bleak import BleakScanner, BleakClient

target_device_name = "YOUR_DEVICE_NAME"
DEVICE_INFO_SERVICE_UUID = "0000180a-0000-1000-8000-00805f9b34fb"
MANUFACTURER_NAME_CHAR_UUID = "00002a29-0000-1000-8000-00805f9b34fb"
MODEL_NUMBER_CHAR_UUID = "00002a24-0000-1000-8000-00805f9b34fb"

async def main():
    # Scan for all available devices
    devices = await BleakScanner.discover()

    for device in devices:
        if device.name == target_device_name:
            print(f"Found target device: {device.name}, {device.address}")

            async with BleakClient(device) as client:
                print(f"Connected: {client.is_connected}")

                services = await client.get_services()
                print("Reading device information...")

                for service in services:
                    if service.uuid == DEVICE_INFO_SERVICE_UUID:
                        print(f"Device Information Service: {service.uuid}")
                        for char in service.characteristics:
                            if char.uuid == MANUFACTURER_NAME_CHAR_UUID:
                                manufacturer_name = await client.read_gatt_char(char)
                                print(f"\tManufacturer Name: {manufacturer_name}")
                            elif char.uuid == MODEL_NUMBER_CHAR_UUID:
                                model_number = await client.read_gatt_char(char)
                                print(f"\tModel Number: {model_number}")

asyncio.run(main())
</code></pre>
<p>執行結果如下圖:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/3.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/3.587.avif 587w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/3.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/3.587.webp 587w" type="image/webp"></source><img alt="Terminal output showing the Bluetooth Device Information Service model number and manufacturer name values" class="article-image" decoding="async" height="59" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble_python_bleak/3.png" width="587" /></picture></p>
<p>後續可以透過 client.read_gatt_char(char) 來取得資料，並且透過 client.write_gatt_char(char, data) 來寫入資料</p>
</li>
</ol>
<h2 id="reference"><a href="#reference"><span class="icon icon-link"></span></a>Reference</h2>
<ol>
<li><a href="https://github.com/hbldh/bleak">Bleak Github</a></li>
<li><a href="https://pypi.org/project/bleak/">Bleak Pypi</a></li>
<li><a href="https://bleak.readthedocs.io/en/latest/index.html">Bleak Official Document</a></li>
<li><a href="https://github.com/ghit42796/Get-Data-from-BLE-with-Python-Bleak">Sample Code Github Repository</a></li>
</ol>]]></content:encoded></item><item><title>Get data from BLE - Concept</title><link>https://fskuan.com/posts/bluetooth/get_data_from_ble/</link><guid isPermaLink="true">https://fskuan.com/posts/bluetooth/get_data_from_ble/</guid><pubDate>Thu, 25 Jan 2024 02:40:52 GMT</pubDate><description>The Concept for How to get data from BLE devices</description><content:encoded><![CDATA[<h2 id="suitable-for"><a href="#suitable-for"><span class="icon icon-link"></span></a>Suitable For</h2>
<ul>
<li>有 Programming 經驗，但沒有接觸過 BLE</li>
<li>想要透過 BLE 來取得資料，但不知道從何下手</li>
<li>著重說明理論 與 如何取得資料</li>
</ul>
<h2 id="concepts"><a href="#concepts"><span class="icon icon-link"></span></a>Concepts</h2>
<h3 id="generic-access-profile-gap"><a href="#generic-access-profile-gap"><span class="icon icon-link"></span></a>Generic Access Profile (GAP)</h3>
<p>在藍牙核心規範中提到 Generic Access Profile (GAP)， GAP 主要負責控制藍牙裝置的 連接模式 和 可見性，同時定義了藍牙裝置如何發現其他裝置、如何建立和終止連接，也管理著裝置的角色（如中央設備或外圍設備）、模式（如可發現模式或連接模式）和安全機制（如配對和加密）</p>
<p>在 GAP 中定義了四種角色，分別為：</p>
<ul>
<li>Broadcaster
<ul>
<li>透過廣播的方式廣播資料，不可連線，但可以被掃描到</li>
</ul>
</li>
<li>Observer
<ul>
<li>利用掃描方式來取得 Broadcaster 廣播的資料</li>
</ul>
</li>
<li>Peripheral
<ul>
<li>接受 Central 的連線，並提供資料給 Central</li>
</ul>
</li>
<li>Central
<ul>
<li>創建與 Peripheral 的連線，並取得資料</li>
</ul>
</li>
</ul>
<p>其中 Central 與 Peripheral 是本次說明的重點，
Peripheral 代表周邊的藍牙裝置(溫度/溼度計)，
Central 則是接受資料的藍牙裝置，例如：手機、電腦等等。</p>
<p>GAP 相關的實作內容通常由藍牙晶片廠商或操作系統處理，本篇專注於資料介接，不討論連接模式、可見性等等的問題</p>
<p>未來有機會再另開一篇討論</p>
<h3 id="attribute-protocol-att"><a href="#attribute-protocol-att"><span class="icon icon-link"></span></a>Attribute Protocol (ATT)</h3>
<p>GATT 是主要討論的重點，GATT 是一種基於 ATT（Attribute Protocol）實現，在介紹 GATT 之前，先介紹 ATT</p>
<p>ATT 主要的格式是 Attritbute ，每個 Attribute 由三個部分組成:</p>
<ul>
<li>16-bit 的 handle
<ul>
<li>一個數字，用來識別 Attribute</li>
</ul>
</li>
<li>定義 Attribute Types 的 UUID</li>
<li>表示裝置公開狀態資訊的 Attribute Value</li>
</ul>
<p>ATT 本身不定義 Attribute Types 具體的意義，此部分由更高層 ( higher-level ) 的 Profiles、GATT 定義</p>
<p>Attribute 可能包含安全性設定，這個設定存在 Attribute Value 中，ATT 本身不處理這個部分，而是由更高層的 Profiles、GATT 負責</p>
<p>大部分的 Attribute Protocol 是採用 Client / Server 模型</p>
<p>Client ( GAP 中的 Central, 手機、電腦等 ) 會發送 Request 給 Server ( GAP 中的 Peripheral, 周邊藍芽裝置 ) ，Server 則會回傳 Response 給 Client</p>
<h3 id="generic-attribute-profile-gatt"><a href="#generic-attribute-profile-gatt"><span class="icon icon-link"></span></a>Generic Attribute Profile (GATT)</h3>
<p>ATT 是一個非常通用的協定，簡單但是在多服務的裝置上可能會有衝突產生</p>
<p>GATT 則是在 ATT 的基礎上定義了一個層級的結構，並且給予了一堆 ATT Attributes 具體的意義</p>
<p>GATT Profile 由三個部分組成:</p>
<ul>
<li>Service</li>
<li>Characteristic</li>
<li>Descriptor</li>
</ul>
<p>結構的概念如下圖:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/GATT_profiles.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/GATT_profiles.572.avif 572w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/GATT_profiles.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/GATT_profiles.572.webp 572w" type="image/webp"></source><img alt="GATT profile hierarchy showing services, included services, characteristics, properties, values, and descriptors" class="article-image" decoding="async" height="687" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble/GATT_profiles.png" width="572" /></picture> [3]</p>
<p>最外層是一個 Profile，Profile 由一個或多個 Service 組成</p>
<p>每個 Service 中會有多個 Characteristics，每個 Characteristics 中會有一個 Properties 和 Value 以及 多個 Descriptors</p>
<h4 id="profiles"><a href="#profiles"><span class="icon icon-link"></span></a>Profiles</h4>
<p>Profile 是一個集合，包含了一個或多個 Services，這些 Services 可能由 Bluetooth SIG 定義，也可能是由開發者自行定義</p>
<p>Profile 通常會定義一個特定的應用，例如：心率監測、體重計、溫度計等等，像是 <a href="https://www.bluetooth.com/specifications/specs/heart-rate-service-1-0/">Heart Rate Profile</a> 中包含了 Heart Rate Service 和 Device Information Service，並描述了這種情境下各自的 Roles 需要做甚麼、連線建立的流程等等</p>
<h4 id="services"><a href="#services"><span class="icon icon-link"></span></a>Services</h4>
<p>Service 也是一個集合，包含了一個或多個 Characteristics，具體描述其中的內容，例如:</p>
<ul>
<li>Service 是做甚麼用的</li>
<li>傳輸的依賴 ( Transport Dependencies )</li>
<li>錯誤的代碼 ( Error Codes )</li>
<li>Service 的宣告 ( Service Declaration )</li>
<li>Service 包含哪些 Characteristics</li>
<li>...</li>
</ul>
<p>例如  <a href="https://www.bluetooth.com/specifications/specs/heart-rate-service-1-0/">Heart Rate Service</a> 中的 Service Declaration 如下:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.680.avif 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.960.avif 960w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.1093.avif 1093w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.680.webp 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.960.webp 960w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.1093.webp 1093w" type="image/webp"></source><img alt="Heart Rate Service declaration specifying a primary service and its assigned UUID" class="article-image article-wide" decoding="async" height="196" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Declaration.png" width="1093" /></picture> [5]</p>
<p>Service Characteristics 描述如下</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_table.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_table.680.avif 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_table.899.avif 899w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_table.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_table.680.webp 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_table.899.webp 899w" type="image/webp"></source><img alt="Heart Rate Service characteristics table listing requirements, properties, and security permissions" class="article-image article-wide" decoding="async" height="539" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_table.png" width="899" /></picture> [5]</p>
<p>說明這個 Service 底下包含以下 Characteristics 以及其所需要實現的功能:</p>
<ul>
<li>Heart Rate Measurement</li>
<li>Heart Rate Measurement Client Characteristic Configuration descriptor</li>
<li>Body Sensor Location</li>
<li>Heart Rate Control Point</li>
</ul>
<h4 id="characteristics"><a href="#characteristics"><span class="icon icon-link"></span></a>Characteristics</h4>
<p>Characteristics 就是具體資料如何傳輸的描述，同樣以上面的 <a href="https://www.bluetooth.com/specifications/specs/heart-rate-service-1-0/">Heart Rate Service</a> 為例，其部分 Characteristics 定義如下:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_1.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_1.680.avif 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_1.884.avif 884w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_1.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_1.680.webp 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_1.884.webp 884w" type="image/webp"></source><img alt="Heart Rate Measurement fields alongside an ECG waveform marking consecutive RR intervals" class="article-image article-wide" decoding="async" height="584" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_1.png" width="884" /></picture> [5]</p>
<p>可以看到其中描述了 Heart Rate Measurement 這個 Characteristics 包含了:</p>
<ul>
<li>heart reate measurement value field</li>
<li>energy expended field</li>
<li>RR-Interval field</li>
<li>解釋了 RR-Interval field 的意義是 Electrocardiogram (ECG) 波形中兩個連續 R 波</li>
</ul>
<p>其部分 Flags Field 定義如下:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.680.avif 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.960.avif 960w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.1217.avif 1217w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.680.webp 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.960.webp 960w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.1217.webp 1217w" type="image/webp"></source><img alt="Heart Rate Measurement flags field rules for UINT8 and UINT16 value formats" class="article-image article-wide" decoding="async" height="501" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Flags.png" width="1217" /></picture> [5]</p>
<p>說明了這個 Flags Field 的第一個 bit 代表 Heart Rate Measurement Value field 是使用 UINT8 還是 UINT16 來表示</p>
<p>如果是 UINT8 則為 0，如果是 UINT16 則為 1</p>
<p>後面還有 Energy Expended Field, RR-Interval Field 等等</p>
<h4 id="descriptors"><a href="#descriptors"><span class="icon icon-link"></span></a>Descriptors</h4>
<p>在上述範例中還有描述 Characteristic Descriptors 的內容</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_2.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_2.680.avif 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_2.870.avif 870w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_2.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_2.680.webp 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_2.870.webp 870w" type="image/webp"></source><img alt="Heart Rate Service requirement for the Client Characteristic Configuration descriptor" class="article-image article-wide" decoding="async" height="151" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_2.png" width="870" /></picture> [5]</p>
<p>其中的具體意義描述在</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_3.480.avif 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_3.680.avif 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_3.912.avif 912w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_3.480.webp 480w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_3.680.webp 680w, https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_3.912.webp 912w" type="image/webp"></source><img alt="Heart Rate Measurement notification behavior and time-sensitive data requirements" class="article-image article-wide" decoding="async" height="189" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/bluetooth/get_data_from_ble/Heart_Rate_Service_Characteristics_3.png" width="912" /></picture> [5]</p>
<h2 id="summary"><a href="#summary"><span class="icon icon-link"></span></a>Summary</h2>
<p>介紹了 BLE 會利用 GAP 來建立連線，並利用 GATT 來傳輸資料</p>
<p>而 GATT Profiles 是一個應用情境，其中包含多個 Services</p>
<p>Service 包含了一個或多個 Characteristics，具體描述其中的 Characteristic, Descriptor 的內容</p>
<p>實際上使用時，會透過 ATT Handle 來取得 Characteristic, Descriptor 的資料</p>
<p>並依照 Service 中描述的格式來解析並取得資料</p>
<p>下一篇會具體介紹如何取得資料</p>
<h2 id="reference"><a href="#reference"><span class="icon icon-link"></span></a>Reference</h2>
<ol>
<li><a href="https://learn.adafruit.com/introduction-to-bluetooth-low-energy?view=all#gatt">https://learn.adafruit.com/introduction-to-bluetooth-low-energy?view=all#gatt</a></li>
<li><a href="https://epxx.co/artigos/bluetooth_gatt.html">https://epxx.co/artigos/bluetooth_gatt.html</a></li>
<li><a href="https://www.bluetooth.com/specifications/specs/core-specification-4-0/">https://www.bluetooth.com/specifications/specs/core-specification-4-0/</a></li>
<li><a href="https://www.bluetooth.com/specifications/specs/heart-rate-profile-1-0/">https://www.bluetooth.com/specifications/specs/heart-rate-profile-1-0/</a></li>
<li><a href="https://www.bluetooth.com/specifications/specs/heart-rate-service-1-0/">https://www.bluetooth.com/specifications/specs/heart-rate-service-1-0/</a></li>
</ol>]]></content:encoded></item><item><title>Get Certifications with Certbot and Nginx Manually</title><link>https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/</link><guid isPermaLink="true">https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/</guid><pubDate>Mon, 11 Dec 2023 04:28:25 GMT</pubDate><description>Get Certifications with Certbot and Nginx Manually</description><content:encoded><![CDATA[<h2 id="motivation"><a href="#motivation"><span class="icon icon-link"></span></a>Motivation</h2>
<ul>
<li>沒有使用支援 Certbot plugin 的 <a href="https://eff-certbot.readthedocs.io/en/latest/using.html#dns-plugins">DNS provider</a></li>
<li>不想把 DNS provider API key 寫在 server 上</li>
<li>不想自動更新 SSL certifications (?!)</li>
<li>無法透過 DNS challenge 取得 certifications ( 網管不給改 DNS records 之類的 )</li>
</ul>
<h2 id="prerequisite"><a href="#prerequisite"><span class="icon icon-link"></span></a>Prerequisite</h2>
<ul>
<li>Certbot</li>
<li>Ngnix</li>
<li>一個網域</li>
</ul>
<h2 id="evnironment"><a href="#evnironment"><span class="icon icon-link"></span></a>Evnironment</h2>
<ul>
<li>Ubuntu 22.04.2 (Certbot 2.8.0)</li>
<li>Windows 10 (Nginx 1.23.3)</li>
</ul>
<h2 id="procedure"><a href="#procedure"><span class="icon icon-link"></span></a>Procedure</h2>
<ol>
<li>
<p>在 Ubuntu 上執行 Certbot</p>
<pre><code>sudo certbot certonly --manual --preferred-challenges=http -d your.domain
</code></pre>
</li>
<li>
<p>Certbot 會提供兩串文字 (如下圖 A 與 B )，並要求你把這個檔案放在網域的根目錄下</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/cerbot.480.avif 480w, https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/cerbot.680.avif 680w, https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/cerbot.856.avif 856w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/cerbot.480.webp 480w, https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/cerbot.680.webp 680w, https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/cerbot.856.webp 856w" type="image/webp"></source><img alt="Certbot manual challenge instructions mapping token content to the required ACME challenge URL" class="article-image article-wide" decoding="async" height="196" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/cerbot.png" width="856" /></picture></p>
</li>
<li>
<p>在 Windows 上設定 Nginx，讓 Nginx 可以存取到這個檔案，設定內容如下</p>
<pre><code>    server {
        listen 80;
        server_name A.your.domain;

        # Handle ACME Challenge for Let's Encrypt
        location ^~ /.well-known/acme-challenge/ {
            root "C:/nginx-1.23.3/http01/";  # 指定存放 ACME challenge 文件的目录

        }
    }
</code></pre>
</li>
<li>
<p>實際路徑如下圖，此路徑下放有一個檔案，檔案名稱為 Certbot 產生的 A 字串，其內容為 B 字串</p>
<pre><code>    C:\nginx-1.23.3\http01\.well-known\acme-challenge
</code></pre>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/acme-challenge.480.avif 480w, https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/acme-challenge.604.avif 604w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/acme-challenge.480.webp 480w, https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/acme-challenge.604.webp 604w" type="image/webp"></source><img alt="Windows Explorer showing ACME challenge token files under the .well-known acme-challenge directory" class="article-image" decoding="async" height="138" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/nginx/get-certifications-with-certbot-and-nginx-manually/acme-challenge.png" width="604" /></picture></p>
</li>
<li>
<p>在 Step1. 的視窗中按下 Enter，Certbot 會去檢查這個檔案是否存在，如果存在並內容正確就會給你 Certifications</p>
</li>
<li>
<p>Certbot 會把 certifications 存在 Ubuntu 上的 <code>/etc/letsencrypt/live/</code> 資料夾下</p>
</li>
</ol>
<h2 id="summary"><a href="#summary"><span class="icon icon-link"></span></a>Summary</h2>
<p>由於無法自動更新，此方法較適用於測試站、測試用途。</p>
<p>長期使用的話，建議還是使用 Certbot plugin 自動取得 Certifications。</p>]]></content:encoded></item><item><title>LeetCode - 33. Search in Rotated Sorted Array</title><link>https://fskuan.com/posts/leetcode/leetcode33/</link><guid isPermaLink="true">https://fskuan.com/posts/leetcode/leetcode33/</guid><pubDate>Tue, 23 May 2023 04:54:30 GMT</pubDate><description>LeetCode33</description><content:encoded><![CDATA[<h2 id="題目-search-in-rotated-sorted-array"><a href="#題目-search-in-rotated-sorted-array"><span class="icon icon-link"></span></a>題目: <a href="https://leetcode.com/problems/search-in-rotated-sorted-array/">Search in Rotated Sorted Array</a></h2>
<pre><code class="language-text">給定一個以升序排列的整數陣列 nums（具有不同的值）。

在傳遞給你的函數之前，可能會在未知的軸心索引 k（1 &lt;= k &lt; nums.length）處對 nums 進行旋轉，
使得結果陣列為
 [nums[k]、nums[k+1]、...、nums[n-1]、nums[0]、nums[1]、...、nums[k-1]]（索引從 0 開始）。
例如，[0,1,2,4,5,6,7] 可能會在軸心索引 3 處旋轉，變為 [4,5,6,7,0,1,2]。

給定可能旋轉後的陣列 nums 和一個整數目標 target，如果 target 存在於 nums 中，
則返回其索引；如果不存在，則返回 -1。

你必須使用 O(log n) 的時間複雜度編寫算法。
</code></pre>
<h2 id="解題思路"><a href="#解題思路"><span class="icon icon-link"></span></a>解題思路</h2>
<p>由於原本的陣列是升序排列的，所以旋轉後一定至少有一邊是有序的。
以 [4,5,6,7,0,1,2] 為例，左邊 [4,5,6,7] 是有序的，右邊 [0,1,2] 也是有序的。
所以我們可以先判斷左邊或右邊是有序的，再判斷 target 是否在有序的那一邊。
如果是就繼續二分搜尋，如果不是就在另一邊繼續二分搜尋。</p>
<p>步驟:</p>
<ol>
<li>二分搜尋法</li>
<li>由於陣列是旋轉過的，所以要先判斷左邊或右邊是有序的</li>
<li>再判斷 target 是否在有序的那一邊。
如果是就繼續二分搜尋，如果不是就在另一邊繼續二分搜尋</li>
</ol>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<h3 id="java"><a href="#java"><span class="icon icon-link"></span></a>Java:</h3>
<p>Runtime 0 ms(100%), Memory 41.5 MB(96.35%)</p>
<pre><code>class Solution {
    public int search(int[] nums, int target) {
        int leftFlag = 0;
        int rightFlag = nums.length -1;
        while(leftFlag &lt;= rightFlag){
            int midFlag = (leftFlag + rightFlag) / 2;
            if (nums[midFlag] == target){
                return midFlag;
            }

            if (nums[leftFlag] &lt;= nums[midFlag]){
                // if target is on left side
                if(nums[leftFlag] &lt;= target &amp;&amp; target &lt;= nums[midFlag]){
                    rightFlag = midFlag -1;
                }else{
                    leftFlag = midFlag + 1;
                }
            }else{
                if(nums[rightFlag] &gt;= target &amp;&amp; nums[midFlag] &lt;= target){
                    leftFlag = midFlag + 1;
                }else{
                    rightFlag = midFlag -1;
                }
            }
        }
        return -1;
    }
}
</code></pre>
<h3 id="python3"><a href="#python3"><span class="icon icon-link"></span></a>Python3:</h3>
<p>Runtime 62 ms(11.45%), Memory 16.7 MB(29.36%)</p>
<pre><code>class Solution:
    def search(self, nums: List[int], target: int) -&gt; int:
        left_flag = 0
        right_flag = len(nums) - 1

        while left_flag &lt;= right_flag:
            mid_flag = (right_flag + left_flag) // 2

            if nums[mid_flag] == target:
                    return mid_flag

            if nums[left_flag] &lt;= nums[mid_flag]:
                if nums[left_flag] &lt;= target and nums[mid_flag] &gt;= target:
                    right_flag = mid_flag - 1
                else:
                    left_flag = mid_flag + 1
            else:
                if nums[right_flag] &gt;= target and nums[mid_flag] &lt;= target:
                    left_flag = mid_flag + 1
                else:
                    right_flag = mid_flag - 1

        return -1
</code></pre>
<h3 id="c"><a href="#c"><span class="icon icon-link"></span></a>C#:</h3>
<p>Runtime 84 ms(81.86%), Memory 39.3 MB(40.9%)</p>
<pre><code>public class Solution {
    public int Search(int[] nums, int target) {
        int LeftFlag = 0;
        int RightFlag = nums.Length - 1;
        while (LeftFlag &lt;= RightFlag) {
            int MidFlag = (LeftFlag + RightFlag) / 2;
            if (nums[MidFlag] == target){
                return MidFlag;
            }

            if (nums[LeftFlag] &lt;= nums[MidFlag]){
                if (nums[LeftFlag] &lt;= target &amp;&amp; target &lt;= nums[MidFlag]){
                    RightFlag = MidFlag - 1;
                }else{
                    LeftFlag = MidFlag + 1;
                }
            }else{
                if (nums[MidFlag] &lt;= target &amp;&amp; target &lt;= nums[RightFlag]){
                    LeftFlag = MidFlag + 1;
                }else{
                    RightFlag = MidFlag - 1;
                }
            }
        }
        return -1;
    }
}
</code></pre>
<h3 id="golang"><a href="#golang"><span class="icon icon-link"></span></a>Golang:</h3>
<p>Runtime 4 ms(51.96%), Memory 2.5 MB(57.42%)</p>
<pre><code>func search(nums []int, target int) int {
	leftFlag := 0
	rightFlag := len(nums) - 1

	for leftFlag &lt;= rightFlag {
		midFlag := (leftFlag + rightFlag) / 2
		if nums[midFlag] == target {
			return midFlag
		}

		if nums[leftFlag] &lt;= nums[midFlag] {
			if nums[leftFlag] &lt;= target &amp;&amp; target &lt;= nums[midFlag] {
				rightFlag = midFlag - 1
			} else {
				leftFlag = midFlag + 1
			}
		} else {
			if nums[midFlag] &lt;= target &amp;&amp; target &lt;= nums[rightFlag] {
				leftFlag = midFlag + 1
			} else {
				rightFlag = midFlag - 1
			}
		}
	}

	return -1
}
</code></pre>
<h3 id="c-1"><a href="#c-1"><span class="icon icon-link"></span></a>C++:</h3>
<p>Runtime 7 ms(32.87%), Memory 11.1 MB(31.74%)</p>
<pre><code>class Solution {
public:
    int search(vector&lt;int&gt;&amp; nums, int target) {
        int intLeftFlag = 0;
        int intRightFlag = nums.size() - 1;
        while(intLeftFlag &lt;= intRightFlag){
            int intMidFlag = (intLeftFlag + intRightFlag) / 2;

            if(nums[intMidFlag] == target){
                return intMidFlag;
            }

            if(nums[intLeftFlag] &lt;= nums[intMidFlag]){
                if (nums[intLeftFlag] &lt;= target &amp;&amp; target &lt;= nums[intMidFlag]){
                    intRightFlag = intMidFlag - 1;
                }else{
                    intLeftFlag = intMidFlag + 1;
                }
            }else{
                if (nums[intMidFlag] &lt;= target &amp;&amp; target &lt;= nums[intRightFlag]){
                    intLeftFlag = intMidFlag + 1;
                }
                else{
                    intRightFlag = intMidFlag - 1;
                }
            }
        }
        return -1;
    }
};
</code></pre>
<h3 id="c-2"><a href="#c-2"><span class="icon icon-link"></span></a>C:</h3>
<p>Runtime 0 ms(100%), Memory 6 MB(64.74%)</p>
<pre><code>int search(int* nums, int numsSize, int target){
    int leftFlag = 0;
    int rightFlag = numsSize - 1;

    while (leftFlag &lt;= rightFlag) {
        int midFlag = (leftFlag + rightFlag) / 2;
        if (nums[midFlag] == target) {
            return midFlag;
        }

        if (nums[leftFlag] &lt;= nums[midFlag]) {
            if (nums[leftFlag] &lt;= target &amp;&amp; target &lt;= nums[midFlag]) {
                rightFlag = midFlag - 1;
            } else {
                leftFlag = midFlag + 1;
            }
        } else {
            if (nums[midFlag] &lt;= target &amp;&amp; target &lt;= nums[rightFlag]) {
                leftFlag = midFlag + 1;
            } else {
                rightFlag = midFlag - 1;
            }
        }
    }

    return -1;
}
</code></pre>]]></content:encoded></item><item><title>LeetCode - 153. Find Minimum in Rotated Sorted Array</title><link>https://fskuan.com/posts/leetcode/leetcode153/</link><guid isPermaLink="true">https://fskuan.com/posts/leetcode/leetcode153/</guid><pubDate>Wed, 17 May 2023 04:34:24 GMT</pubDate><description>LeetCode - 153. Find Minimum in Rotated Sorted Array</description><content:encoded><![CDATA[<h2 id="題目-153-find-minimum-in-rotated-sorted-array"><a href="#題目-153-find-minimum-in-rotated-sorted-array"><span class="icon icon-link"></span></a>題目: <a href="https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/">153. Find Minimum in Rotated Sorted Array</a></h2>
<p>假設有一個長度為 n 的陣列，按照升序排序，並且進行了 1 到 n 次旋轉。</p>
<p>例如，陣列 nums = [0,1,2,4,5,6,7] 可能會變成：
[4,5,6,7,0,1,2] 如果旋轉了 4 次。
[0,1,2,4,5,6,7] 如果旋轉了 7 次。</p>
<p>注意，將陣列 [a[0], a[1], a[2], ..., a[n-1]]
旋轉 1 次會得到陣列 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]。</p>
<p>給定已排序且元素唯一的旋轉陣列 nums，請回傳該陣列中的最小元素。</p>
<p>你必須撰寫一個在 O(log n) 時間內運行的演算法。</p>
<h2 id="解題思路"><a href="#解題思路"><span class="icon icon-link"></span></a>解題思路</h2>
<p>以二元搜尋樹的方式，找出最小值。</p>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<h3 id="java"><a href="#java"><span class="icon icon-link"></span></a>Java:</h3>
<p>Runtime 0 ms(100%), Memory 42.4 MB(29.4%)</p>
<pre><code>class Solution {
    public class ValueDuplicateException extends Exception {
        public ValueDuplicateException(String message) {
            super(message);
        }
    }

    public int findMin(int[] nums) {
        int leftFlag = 0;
        int rightFlag = nums.length - 1;

        while(leftFlag &lt; rightFlag){
            int midFlag = (leftFlag + rightFlag) / 2;
            // if mid value is greater then right, means min value is in the right side
            if (nums[midFlag] &gt; nums[rightFlag]){
                leftFlag = midFlag + 1;
            }
            // if mid value is less then right, means min value is in the left side
            else if(nums[midFlag] &lt; nums[rightFlag]){
                rightFlag = midFlag;
            }
            // if mid value equals to right, means it can not happen.
            // there is no duplicate value. Raise Exception.
            else{
                // throw new ValueDuplicateException("Value is duplicate");
            }
        }
        return nums[leftFlag];
    }
}
</code></pre>
<h3 id="python3"><a href="#python3"><span class="icon icon-link"></span></a>Python3:</h3>
<p>Runtime 57 ms(17.66%), Memory 16.7 MB(10.13%)</p>
<pre><code>class Solution:
    class ValueDuplicateException(Exception):
        pass

    def findMin(self, nums: List[int]) -&gt; int:
        # variant binary search
        left_flag = 0
        right_flag = len(nums) - 1

        while left_flag &lt; right_flag:
            mid_flag = (left_flag + right_flag) // 2
            # if mid value is greater then right, means min value is in the right side
            if nums[mid_flag] &gt; nums[right_flag]:
                left_flag = mid_flag + 1
            # if mid value is less then right, means min value is in the left side
            elif nums[mid_flag] &lt; nums[right_flag]:
                right_flag = mid_flag
            # if mid value equals to right, means it can not happen.
            # there is no duplicate value. Raise Exception.
            elif nums[mid_flag] == nums[right_flag]:
                raise ValueDuplicateException

        return nums[left_flag]
</code></pre>
<h3 id="c"><a href="#c"><span class="icon icon-link"></span></a>C#:</h3>
<p>Runtime 92 ms(33.48%), Memory 38.9 MB(57.81%)</p>
<pre><code>using System;

public class Solution {
    public class ValueDuplicateException : Exception {
        public ValueDuplicateException(string message) : base(message) {
        }
    }

    public int FindMin(int[] nums) {
        int leftFlag = 0;
        int rightFlag = nums.Length - 1;

        while (leftFlag &lt; rightFlag) {
            int midFlag = (leftFlag + rightFlag) / 2;

            if (nums[midFlag] &gt; nums[rightFlag]) {
                leftFlag = midFlag + 1;
            }
            else if (nums[midFlag] &lt; nums[rightFlag]) {
                rightFlag = midFlag;
            }
            else {
                throw new ValueDuplicateException("Value is duplicate");
            }
        }

        return nums[leftFlag];
    }
}
</code></pre>
<h3 id="golang"><a href="#golang"><span class="icon icon-link"></span></a>Golang:</h3>
<p>Runtime 4 ms(56.99%), Memory 2.5 MB(98.74%)</p>
<pre><code>func findMin(nums []int) int {
    leftFlag := 0
	rightFlag := len(nums) - 1

	for leftFlag &lt; rightFlag {
		midFlag := (leftFlag + rightFlag) / 2
		if nums[midFlag] &gt; nums[rightFlag] {
            // if mid value is greater than right, means the minimum value is in the right side
			leftFlag = midFlag + 1
		} else if nums[midFlag] &lt; nums[rightFlag] {
            // if mid value is less than right, means the minimum value is in the left side
			rightFlag = midFlag
		} else if nums[midFlag] == nums[rightFlag] {
            // if mid value equals right, it means it cannot happen.
            // There is no duplicate value. Throw an exception.

		}
	}

	return nums[leftFlag]

}
</code></pre>
<h3 id="c-1"><a href="#c-1"><span class="icon icon-link"></span></a>C++:</h3>
<p>Runtime 5 ms(39.12%), Memory 10.1 MB(70.97%)</p>
<pre><code>class Solution {
public:
    int findMin(vector&lt;int&gt;&amp; nums) {
        int left_flag = 0;
        int right_flag = nums.size() - 1;
        while (left_flag &lt; right_flag) {
            int mid_flag = ( left_flag + right_flag ) / 2;

            if (nums[mid_flag] &gt; nums[right_flag]){
                left_flag = mid_flag + 1;
            }
            else if (nums[mid_flag] &lt; nums[right_flag]){
                right_flag = mid_flag;
            }
            else{
                // throw ValueDuplicateException;
            }
        }

        return nums[left_flag];
    }
};
</code></pre>
<h3 id="c-2"><a href="#c-2"><span class="icon icon-link"></span></a>C:</h3>
<p>Runtime 3 ms(74.5%), Memory 5.9 MB(92.41%)</p>
<pre><code>int findMin(int* nums, int numsSize){
        int leftFlag = 0;
        int rightFlag = numsSize - 1;

        while (leftFlag &lt; rightFlag) {
            int midFlag = (leftFlag + rightFlag) / 2;
            // if mid value is greater than right, means the minimum value is in the right side
            if (nums[midFlag] &gt; nums[rightFlag]) {
                leftFlag = midFlag + 1;
            }
            // if mid value is less than right, means the minimum value is in the left side
            else if (nums[midFlag] &lt; nums[rightFlag]) {
                rightFlag = midFlag;
            }
            // if mid value equals right, it means it cannot happen.
            // There is no duplicate value. Throw an exception.
            else if (nums[midFlag] == nums[rightFlag]) {
                // throw ValueDuplicateException();
            }
        }

        return nums[leftFlag];
}
</code></pre>]]></content:encoded></item><item><title>LeetCode - 152. Maximum Product Subarray</title><link>https://fskuan.com/posts/leetcode/leetcode152/</link><guid isPermaLink="true">https://fskuan.com/posts/leetcode/leetcode152/</guid><pubDate>Wed, 10 May 2023 04:45:47 GMT</pubDate><description>LeetCode - 152. Maximum Product Subarray</description><content:encoded><![CDATA[<h2 id="題目-152-maximum-product-subarray"><a href="#題目-152-maximum-product-subarray"><span class="icon icon-link"></span></a>題目: <a href="https://leetcode.com/problems/maximum-product-subarray/">152. Maximum Product Subarray</a></h2>
<p>給定一個整數陣列 <code>nums</code>，找出一個子陣列，使得其元素相乘的積最大，並回傳此最大積。
所有測試案例都保證答案在32位元整數的範圍內。</p>
<h2 id="解題思路"><a href="#解題思路"><span class="icon icon-link"></span></a>解題思路</h2>
<p>可以使用一個變數currentProduct來記錄當前的子陣列元素積
以及一個變數 <code>maxProduct</code> 來記錄迄今為止找到的最大子陣列元素積。
接下來，遍歷整個陣列，並在每個元素上更新這兩個變數。</p>
<p>對於每個元素，可以將其乘到currentProduct上，
並將maxProduct與currentProduct比較取最大值。
如果currentProduct變為了 0，表示之前的元素積已經失去了意義，需要重置currentProduct為 1。</p>
<p>最後，需要從陣列的末尾開始再遍歷一次，因為有些最大積可能是由負數元素構成的。
在這次遍歷中，可以使用和前一次遍歷相同的方法來計算每個子陣列的元素積並更新maxProduct。</p>
<p>最後，只需要回傳maxProduct即可。</p>
<p>這個方法的時間複雜度是 $O(n)$，其中 $n$ 是陣列的長度，空間複雜度是 $O(1)$。</p>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<h3 id="java"><a href="#java"><span class="icon icon-link"></span></a>Java:</h3>
<p>Runtime 1 ms(87.54%), Memory 43.1 MB(11.48%)</p>
<pre><code>class Solution {
    public int maxProduct(int[] nums) {
        int current_product = 1;
        int max_product = Integer.MIN_VALUE;
        for(int i=0; i&lt;nums.length; i++){
            current_product *= nums[i];
            max_product = Math.max(current_product, max_product);
            if(current_product == 0){
                current_product = 1;
            }
        }

        current_product = 1;
        for(int i=nums.length - 1; i&gt;=0; i--){
            current_product *= nums[i];
            max_product = Math.max(current_product, max_product);
            if(current_product == 0){
                current_product = 1;
            }
        }
        return max_product;
    }
}
</code></pre>
<h3 id="python3"><a href="#python3"><span class="icon icon-link"></span></a>Python3:</h3>
<p>Runtime 86 ms(67.6%), Memory 16.8 MB(11.23%)</p>
<pre><code>import sys

class Solution:
    def maxProduct(self, nums: List[int]) -&gt; int:
        current_product = 1
        max_product = -sys.maxsize - 1
        for i in range(len(nums)):
            current_product *= nums[i]
            max_product = max(max_product, current_product)
            if current_product == 0:
                current_product = 1

        current_product = 1
        for i in range(len(nums)-1, -1, -1):
            current_product *= nums[i]
            max_product = max(max_product, current_product)
            if current_product == 0:
                current_product = 1
        return max_product

</code></pre>
<h3 id="c"><a href="#c"><span class="icon icon-link"></span></a>C#:</h3>
<p>Runtime 88 ms(74.46%), Memory 40.3 MB(31.52%)</p>
<pre><code>public class Solution {
    public int MaxProduct(int[] nums) {
        int current_product = 1;
        int max_product = int.MinValue;
        for(int i=0; i&lt;nums.Length; i++){
            current_product *= nums[i];
            max_product = Math.Max(current_product, max_product);
            if(current_product == 0){
                current_product = 1;
            }
        }

        current_product = 1;
        for(int i=nums.Length - 1; i&gt;=0; i--){
            current_product *= nums[i];
            max_product = Math.Max(current_product, max_product);
            if(current_product == 0){
                current_product = 1;
            }
        }
        return max_product;
    }
}
</code></pre>
<h3 id="golang"><a href="#golang"><span class="icon icon-link"></span></a>Golang:</h3>
<p>Runtime 4 ms(88.15%), Memory 3.4 MB(99.45%)</p>
<pre><code>func maxProduct(nums []int) int {
    currentProduct := 1
    maxProduct := math.MinInt32
    for i := 0; i &lt; len(nums); i++ {
        currentProduct *= nums[i]
        maxProduct = max(currentProduct, maxProduct)
        if currentProduct == 0 {
            currentProduct = 1
        }
    }

    currentProduct = 1
    for i := len(nums) - 1; i &gt;= 0; i-- {
        currentProduct *= nums[i]
        maxProduct = max(currentProduct, maxProduct)
        if currentProduct == 0 {
            currentProduct = 1
        }
    }
    return maxProduct
}

func max(x, y int) int {
    if x &gt; y {
        return x
    }
    return y
}
</code></pre>
<h3 id="c-1"><a href="#c-1"><span class="icon icon-link"></span></a>C++:</h3>
<p>Runtime 15 ms(10.35%), Memory 13.7 MB(86.21%)</p>
<pre><code>#include &lt;algorithm&gt;

class Solution {
public:
    int maxProduct(vector&lt;int&gt;&amp; nums) {
        int currentProduct = 1;
        int maxProduct = INT_MIN;
        for(int i=0; i&lt;nums.size(); i++){
            currentProduct *= nums[i];
            maxProduct = std::max(currentProduct, maxProduct);
            if(currentProduct == 0){
                currentProduct = 1;
            }
        }

        currentProduct = 1;
        for(int i=nums.size() - 1; i&gt;=0; i--){
            currentProduct *= nums[i];
            maxProduct = std::max(currentProduct, maxProduct);
            if(currentProduct == 0){
                currentProduct = 1;
            }
        }
        return maxProduct;
    }
};
</code></pre>
<h3 id="c-2"><a href="#c-2"><span class="icon icon-link"></span></a>C:</h3>
<p>Runtime 7 ms(69.93%), Memory 6.7 MB(44.76%)</p>
<pre><code>#include &lt;limits.h&gt;

int max(int a, int b) {
    return a &gt; b ? a : b;
}

int maxProduct(int* nums, int numsSize) {
    int currentProduct = 1;
    int maxProduct = INT_MIN;
    for(int i = 0; i &lt; numsSize; i++){
        currentProduct *= nums[i];
        maxProduct = max(currentProduct, maxProduct);
        if(currentProduct == 0){
            currentProduct = 1;
        }
    }

    currentProduct = 1;
    for(int i = numsSize - 1; i &gt;= 0; i--){
        currentProduct *= nums[i];
        maxProduct = max(currentProduct, maxProduct);
        if(currentProduct == 0){
            currentProduct = 1;
        }
    }
    return maxProduct;
}

</code></pre>]]></content:encoded></item><item><title>LeetCode - 53. Maximum Subarray</title><link>https://fskuan.com/posts/leetcode/leetcode53/</link><guid isPermaLink="true">https://fskuan.com/posts/leetcode/leetcode53/</guid><pubDate>Tue, 14 Feb 2023 13:31:14 GMT</pubDate><description>LeetCode53</description><content:encoded><![CDATA[<h2 id="題目-maximum-subarray"><a href="#題目-maximum-subarray"><span class="icon icon-link"></span></a>題目: <a href="https://leetcode.com/problems/maximum-subarray/">Maximum Subarray</a></h2>
<p>給定一個整數數組 nums，找到其和最大的子數組，並返回該子數組的和。</p>
<h2 id="解題思路"><a href="#解題思路"><span class="icon icon-link"></span></a>解題思路</h2>
<p>Kadane's Algorithm:用於求解最大子數組和的動態規劃算法。
從數組的第一個元素開始遍歷，依次計算包含當前元素的所有子數組的和，並選擇其中和最大的子數組作為當前位置的最大子數組。同時，還需要維護一個全局最大子數組和的變量，以便在遍歷過程中不斷更新最大值。
時間複雜度為 O(n)，空間複雜度為 O(1)。</p>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<h3 id="java"><a href="#java"><span class="icon icon-link"></span></a>Java:</h3>
<p>Runtime 2 ms(18%), Memory 73.7 MB(5.1%)</p>
<pre><code>class Solution {
    public int maxSubArray(int[] nums) {
        int current_sum = nums[0], max_sum = nums[0];
        for (int i=1; i&lt;nums.length; i++){
            current_sum = Math.max(nums[i], nums[i] + current_sum);
            max_sum = Math.max(max_sum, current_sum);
        }
        return max_sum;
    }
}
</code></pre>
<h3 id="python3"><a href="#python3"><span class="icon icon-link"></span></a>Python3:</h3>
<p>Runtime 808 ms(24.28%),Memory 28.6 MB(83.1%)</p>
<pre><code>class Solution:
    def maxSubArray(self, nums: List[int]) -&gt; int:
        current_sum = nums[0]
        max_sum = current_sum
        for i in range(1, len(nums)):
            current_sum = max(nums[i], nums[i] + current_sum)
            max_sum = max(max_sum, current_sum)
        return max_sum
</code></pre>
<h3 id="c"><a href="#c"><span class="icon icon-link"></span></a>C#:</h3>
<p>Runtime 251 ms(5.3%), Memory 49.2 MB(97.61%)</p>
<pre><code>public class Solution {
    public int MaxSubArray(int[] nums) {
        int current_sum = nums[0];
        int max_sum = current_sum;
        for (int i=1; i&lt;nums.Length; i++){
            current_sum = Math.Max(nums[i], nums[i] + current_sum);
            max_sum = Math.Max(max_sum, current_sum);
        }
        return max_sum;
    }
}
</code></pre>
<h3 id="golang"><a href="#golang"><span class="icon icon-link"></span></a>Golang:</h3>
<p>Runtime 198 ms(5.19%), Memory 9.2 MB(56.15%)</p>
<pre><code>func maxSubArray(nums []int) int {
    current_sum := nums[0];
    max_sum := current_sum;
    for i:=1; i&lt;len(nums); i++{
        current_sum = max(nums[i], nums[i] + current_sum);
        max_sum = max(max_sum, current_sum);
    }
    return max_sum;
}
func max(a int, b int) int {
    if a &gt; b {
        return a;
    }else{
        return b;
    }
}
</code></pre>
<h3 id="c-1"><a href="#c-1"><span class="icon icon-link"></span></a>C++:</h3>
<p>Runtime 122 ms(45.5%), Memory 67.8 MB(17.85%)</p>
<pre><code>class Solution {
public:
    int maxSubArray(vector&lt;int&gt;&amp; nums) {
        int current_sum = nums[0], max_sum = nums[0];
        for (int i=1; i&lt;nums.size(); i++){
            current_sum = max(nums[i], nums[i] + current_sum);
            max_sum = max(max_sum, current_sum);
        }
        return max_sum;
    }
};
</code></pre>
<h3 id="c-2"><a href="#c-2"><span class="icon icon-link"></span></a>C:</h3>
<p>Runtime 109 ms(94.49%), Memory 12.1 MB(100%)</p>
<pre><code>int maxSubArray(int* nums, int numsSize){
    int current_sum = nums[0];
    int max_sum = current_sum;
    for (int i=1; i&lt;numsSize; i++){
        current_sum = fmax(nums[i], nums[i] + current_sum);
        max_sum = fmax(max_sum, current_sum);
    }
    return max_sum;
}
</code></pre>]]></content:encoded></item><item><title>LeetCode - 238. Product of Array Except Self</title><link>https://fskuan.com/posts/leetcode/leetcode238/</link><guid isPermaLink="true">https://fskuan.com/posts/leetcode/leetcode238/</guid><pubDate>Wed, 18 Jan 2023 15:46:55 GMT</pubDate><description>LeetCode238</description><content:encoded><![CDATA[<h2 id="題目-53-product-of-array-except-self"><a href="#題目-53-product-of-array-except-self"><span class="icon icon-link"></span></a>題目: <a href="https://leetcode.com/problems/product-of-array-except-self/">53. Product of Array Except Self</a></h2>
<p>給一陣列，逐個取其左、右方所有數字之乘積，但不包含自身；並返回此陣列。</p>
<h2 id="解題思路"><a href="#解題思路"><span class="icon icon-link"></span></a>解題思路</h2>
<p>Iterate時，同時記錄prefix(左方乘積之值)、postfix(右方乘積之值)，並把值計算於對應之slice。</p>
<p>單一次的iterate時，同時處理nums[i]之prefix與nums[length - i - 1]之postfix。</p>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<h3 id="java"><a href="#java"><span class="icon icon-link"></span></a>Java:</h3>
<p>Runtime 3 ms(21.87%), Memory 50.5 MB(70.38%)</p>
<pre><code>class Solution {
    public int[] productExceptSelf(int[] nums) {
        int length = nums.length;
        int prefix = 1, postfix = 1;
        int[] answer = new int[length];
        Arrays.fill(answer, 1);
        for(int i=0; i&lt;length; i++){
            // multiple prefix
            answer[i] *= prefix;
            prefix *= nums[i];

            answer[length - i - 1] *= postfix;
            postfix *= nums[length - i - 1];
        }
        return answer;
    }
}
</code></pre>
<h3 id="python3"><a href="#python3"><span class="icon icon-link"></span></a>Python3:</h3>
<p>Runtime 246 ms(58.82%),Memory 21.3 MB(47.26%)</p>
<pre><code>class Solution:
    def productExceptSelf(self, nums: List[int]) -&gt; List[int]:
        lengthOfInput = len(nums)
        answer = [1] * lengthOfInput
        prefix = 1
        postfix = 1
        for i in range(lengthOfInput):
            answer[i] *= prefix
            prefix *= nums[i]
            answer[lengthOfInput - i - 1] *= postfix
            postfix *= nums[lengthOfInput - i - 1]
        return answer

</code></pre>
<h3 id="c"><a href="#c"><span class="icon icon-link"></span></a>C#:</h3>
<p>Runtime 180 ms(47.39%), Memory 53.9 MB(61.68%)</p>
<pre><code>public class Solution {
    public int[] ProductExceptSelf(int[] nums) {
        int length = nums.Length;
        int[] answer = new int[length];
        Array.Fill(answer, 1);
        int prefix = 1, postfix = 1;
        for(int i = 0; i &lt; length; i++){
            answer[i] *= prefix;
            prefix *= nums[i];

            answer[length - i - 1] *= postfix;
            postfix *= nums[length - i - 1];
        }
        return answer;
    }
}
</code></pre>
<h3 id="golang"><a href="#golang"><span class="icon icon-link"></span></a>Golang:</h3>
<p>Runtime 27 ms(64.43%), Memory 7.6 MB(39.3%)</p>
<pre><code>func productExceptSelf(nums []int) []int {
    var length int = len(nums)
    answer := make([]int, length)
    for i := range answer {
        answer[i] = 1
    }
    var prefix int = 1
    var postfix int = 1

    for i := 0; i &lt; length; i = i+1 {
        answer[i] *= prefix
        prefix *= nums[i]

        answer[length - i - 1] *= postfix
        postfix *= nums[length - i - 1]
    }

    return answer
}
</code></pre>
<h3 id="c-1"><a href="#c-1"><span class="icon icon-link"></span></a>C++:</h3>
<p>Runtime 16 ms(98.12%), Memory 24.1 MB(54.53%)</p>
<pre><code>class Solution {
public:
    vector&lt;int&gt; productExceptSelf(vector&lt;int&gt;&amp; nums) {
        int length = nums.size();
        int prefix = 1, postfix = 1;
        vector&lt;int&gt; answer(length, 1);

        for(int i=0; i&lt;length; i++){
            answer[i] *= prefix;
            prefix *= nums[i];

            answer[length - i - 1] *= postfix;
            postfix *= nums[length -i -1];
        }
        return answer;
    }
};
</code></pre>]]></content:encoded></item><item><title>LeetCode - 217. Contains Duplicate</title><link>https://fskuan.com/posts/leetcode/leetcode217/</link><guid isPermaLink="true">https://fskuan.com/posts/leetcode/leetcode217/</guid><pubDate>Wed, 11 Jan 2023 17:02:24 GMT</pubDate><description>LeetCode217</description><content:encoded><![CDATA[<h2 id="題目-217-contains-duplicate"><a href="#題目-217-contains-duplicate"><span class="icon icon-link"></span></a>題目: <a href="https://leetcode.com/problems/contains-duplicate/">217. Contains Duplicate</a></h2>
<p>給一數字陣列，找出裡面是否有重複的數字。</p>
<h2 id="解題思路"><a href="#解題思路"><span class="icon icon-link"></span></a>解題思路</h2>
<p>思路一: iterate陣列nums，並把看過的num存在set中；如果num已經在set中，則返回True；
否則iterate結束(代表沒有找到有重複的num)則返回False。</p>
<p>思路二: 排序陣列nums後，從第二個(index = 1)開始iterate整個陣列，並檢查前一個num與當前num是否相同，相同則重複；
否則iterate結束(代表沒有找到有重複的num)則返回False。</p>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<h3 id="java"><a href="#java"><span class="icon icon-link"></span></a>Java:</h3>
<p>Runtime 5 ms(96.38%), Memory 50.6 MB(96.43%)</p>
<pre><code>class Solution {
    public boolean containsDuplicate(int[] nums) {
        HashSet&lt;Integer&gt; viewed = new HashSet&lt;Integer&gt;();
        for(int i=0; i&lt;nums.length; i++){
            if(!viewed.add(nums[i])){
                return true;
            }
        }
        return false;
    }
}
</code></pre>
<h3 id="python3"><a href="#python3"><span class="icon icon-link"></span></a>Python3:</h3>
<p>Runtime 464 ms(85.88%), Memory 25.9 MB(91.76%)</p>
<pre><code>class Solution:
    def containsDuplicate(self, nums: List[int]) -&gt; bool:
        viewed = set()
        for i in range(len(nums)):
            if nums[i] in viewed:
                return True
            else:
                viewed.add(nums[i])
        return False
</code></pre>
<h3 id="c"><a href="#c"><span class="icon icon-link"></span></a>C#:</h3>
<p>Runtime 199 ms(66.51%), Memory 52.2 MB(36.17%)</p>
<pre><code>public class Solution {
    public bool ContainsDuplicate(int[] nums) {
        HashSet&lt;int&gt; viewed = new HashSet&lt;int&gt;();
        for(int i=0; i&lt;nums.Length; i++){
            if(!viewed.Add(nums[i])){
                return true;
            }
        }
        return false;
    }
}
</code></pre>
<h3 id="golang"><a href="#golang"><span class="icon icon-link"></span></a>Golang:</h3>
<p>Runtime 65 ms(97.15%), Memory 8.9 MB(55.31%)</p>
<pre><code>type void struct{}
var empty void

func containsDuplicate(nums []int) bool {
    viewed := make(map[int]void);
    for _, num := range nums {
        if _, ok := viewed[num]; ok {
            return true;
        }else{
            viewed[num] = empty;
        }
    }
    return false;
}
</code></pre>
<h3 id="c-1"><a href="#c-1"><span class="icon icon-link"></span></a>C++:</h3>
<p>Runtime 112 ms(68.51%), Memory 51.5 MB(52.15%)</p>
<pre><code>class Solution {
public:
    bool containsDuplicate(vector&lt;int&gt;&amp; nums) {
        unordered_set&lt;int&gt; viewed;
        for(int i=0; i&lt;nums.size(); i++){
            if (viewed.count(nums[i])){
                return true;
            }else{
                viewed.insert(nums[i]);
            }
        }
        return false;
    }
};
</code></pre>
<h3 id="c-2"><a href="#c-2"><span class="icon icon-link"></span></a>C:</h3>
<p>Runtime 125 ms(73.38%), Memory 12.6 MB(90.14%)</p>
<pre><code>int compare(const int *a, const int *b){
    return *a - *b;
}

bool containsDuplicate(int* nums, int numsSize){
    qsort(nums, numsSize, sizeof(int), compare);

    for(int i=1; i&lt;numsSize; i++){
        if (nums[i - 1] == nums[i]){
            return true;
        }
    }
    return false;
}
</code></pre>]]></content:encoded></item><item><title>LeetCode - 121. Best Time to Buy and Sell Stock</title><link>https://fskuan.com/posts/leetcode/leetcode121/</link><guid isPermaLink="true">https://fskuan.com/posts/leetcode/leetcode121/</guid><pubDate>Mon, 09 Jan 2023 15:45:21 GMT</pubDate><description>LeetCode - 121. Best Time to Buy and Sell Stock</description><content:encoded><![CDATA[<h2 id="題目-121-best-time-to-buy-and-sell-stock"><a href="#題目-121-best-time-to-buy-and-sell-stock"><span class="icon icon-link"></span></a>題目: <a href="https://leetcode.com/problems/best-time-to-buy-and-sell-stock/">121. Best Time to Buy and Sell Stock</a></h2>
<p>給一陣列(prices)，陣列中各element代表各天的股價。
你可以在某一天買進，在某一天賣出，求最大利潤。
如果沒有最大利潤，則返回0。</p>
<h2 id="解題思路"><a href="#解題思路"><span class="icon icon-link"></span></a>解題思路</h2>
<p>找最大、最小值，但最小值的索引值需小於最大值的索引值，因為你不能穿越時空回到過去賣股票。</p>
<p>每次iterate時，以當前股價計算profit，並記錄best profit與最小值。</p>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<h3 id="java"><a href="#java"><span class="icon icon-link"></span></a>Java:</h3>
<p>Runtime 1 ms(100%), Memory 59 MB(82.59%)</p>
<pre><code>class Solution {
    public int maxProfit(int[] prices) {
        int bestProfit = 0;
        int min = Integer.MAX_VALUE;
        for (int i = 0; i &lt; prices.length; i++){
            int profit = prices[i] - min;
            if (profit &gt; bestProfit){
                bestProfit = profit;
            }
            if (prices[i] &lt; min){
                min = prices[i];
            }
        }
        return bestProfit;
    }
}
</code></pre>
<h3 id="python3"><a href="#python3"><span class="icon icon-link"></span></a>Python3:</h3>
<p>Runtime 940 ms(98.55%),Memory 25 MB(86.59%)</p>
<pre><code>class Solution:
    def maxProfit(self, prices: List[int]) -&gt; int:
        bestProfit = 0
        minPrice = 10001
        for i in range(len(prices)):
            profit = prices[i] - minPrice
            if profit &gt; bestProfit:
                bestProfit = profit
            if prices[i] &lt; minPrice:
                minPrice = prices[i]
        return bestProfit
</code></pre>
<h3 id="c"><a href="#c"><span class="icon icon-link"></span></a>C#:</h3>
<p>Runtime 238 ms(84.31%), Memory 49.5 MB(29.32%)</p>
<pre><code>public class Solution {
    public int MaxProfit(int[] prices) {
        int bestProfit = 0;
        int min = Int32.MaxValue;
        for (int i = 0; i &lt; prices.Length; i++){
            int profit = prices[i] - min;
            bestProfit = Math.Max(profit, bestProfit);
            min = Math.Min(prices[i], min);
        }
        return bestProfit;
    }
}
</code></pre>
<h3 id="golang"><a href="#golang"><span class="icon icon-link"></span></a>Golang:</h3>
<p>Runtime 119 ms(90.37%), Memory 8.8 MB(37.19%)</p>
<pre><code>func maxProfit(prices []int) int {
    bestProfit := 0;
    min := math.MaxInt32;
    for i := 0; i &lt; len(prices); i++ {
        var profit = prices[i] - min;
        if profit &gt; bestProfit{
            bestProfit = profit;
        }
        if prices[i] &lt; min {
            min = prices[i]
        }
    }
    return bestProfit;
}
</code></pre>
<h3 id="c-1"><a href="#c-1"><span class="icon icon-link"></span></a>C++:</h3>
<p>Runtime 131 ms(92.72), Memory 93.3 MB(54.15)</p>
<pre><code>class Solution {
public:
    int maxProfit(vector&lt;int&gt;&amp; prices) {
        int bestProfit = 0, minPrice = INT_MAX;
        for (int i=0; i&lt;prices.size(); i++){
            int profit = prices[i] - minPrice;
            minPrice = min(prices[i], minPrice);
            bestProfit = max(profit, bestProfit);
        }
        return bestProfit;
    }
};
</code></pre>
<h3 id="c-2"><a href="#c-2"><span class="icon icon-link"></span></a>C:</h3>
<p>Runtime 145 ms(82.69%), Memory 13.1 MB(37.69)</p>
<pre><code>int maxProfit(int* prices, int pricesSize){
    int bestProfit = 0, minPrice = INT_MAX;
    for (int i=0; i&lt;pricesSize; i++){
        int profit = prices[i] - minPrice;
        minPrice = prices[i] &lt; minPrice ? prices[i] : minPrice;
        bestProfit = profit &gt; bestProfit ? profit : bestProfit;
    }
    return bestProfit;
}
</code></pre>]]></content:encoded></item><item><title>LeetCode - 1. TwoSum</title><link>https://fskuan.com/posts/leetcode/leetcode1/</link><guid isPermaLink="true">https://fskuan.com/posts/leetcode/leetcode1/</guid><pubDate>Sun, 08 Jan 2023 15:16:57 GMT</pubDate><description>LeetCode - 1. TwoSum</description><content:encoded><![CDATA[<h2 id="題目-1-two-sum"><a href="#題目-1-two-sum"><span class="icon icon-link"></span></a>題目 <a href="https://leetcode.com/problems/two-sum/">1. Two Sum</a></h2>
<p>找出相加等於target的兩數。</p>
<h2 id="解題思路"><a href="#解題思路"><span class="icon icon-link"></span></a>解題思路</h2>
<p>題目自帶官方解法。</p>
<h2 id="code"><a href="#code"><span class="icon icon-link"></span></a>Code</h2>
<h3 id="java"><a href="#java"><span class="icon icon-link"></span></a>Java</h3>
<p>Runtime 2 ms, Memory 42.7 MB</p>
<pre><code>class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
        for (int i = 0; i &lt; nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] { map.get(complement), i };
            }
            map.put(nums[i], i);
        }
        return null;
    }
}
</code></pre>
<h3 id="python3"><a href="#python3"><span class="icon icon-link"></span></a>Python3</h3>
<p>Runtime 56 ms,Memory 15.2 MB</p>
<pre><code>class Solution:
    def twoSum(self, nums: List[int], target: int) -&gt; List[int]:
        hashmap = {}
        for i in range(len(nums)):
            complement = target - nums[i]
            if complement in hashmap:
                return [i, hashmap[complement]]
            hashmap[nums[i]] = i
</code></pre>
<h3 id="c"><a href="#c"><span class="icon icon-link"></span></a>C#</h3>
<p>Runtime 146 ms, Memory 44.8 MB</p>
<pre><code>public class Solution {
    public int[] TwoSum(int[] nums, int target) {
        Dictionary&lt;int, int&gt; map = new Dictionary&lt;int, int&gt;();
        for(int i = 0; i &lt; nums.Length; i++){
            int num = nums[i];
            int complement = target - num;
            if (map.ContainsKey(complement)){
                return new int[] {map[complement], i};
            }else if(!map.ContainsKey(num)){
                map.Add(num, i);
            }
        }
        return null;
    }
}
</code></pre>
<h3 id="golang"><a href="#golang"><span class="icon icon-link"></span></a>Golang</h3>
<p>Runtime 9 ms, Memory 4.2 MB</p>
<pre><code>func twoSum(nums []int, target int) []int {
    mapNum := make(map[int]int)
    for i, num := range nums{
        var complement int = target - num
        // check val is in map
        if val, ok := mapNum[complement]; ok {
            return []int{i, val}
        }
        mapNum[num] = i
    }
    return nil
}
</code></pre>
<h3 id="c-1"><a href="#c-1"><span class="icon icon-link"></span></a>C++</h3>
<p>Runtime 9 ms, Memory 11.2 MB</p>
<pre><code>class Solution {
public:
    vector&lt;int&gt; twoSum(vector&lt;int&gt;&amp; nums, int target) {
        map&lt;int, int&gt; mapNum;
        for(int i = 0; i &lt; nums.size(); i++){
            int num = nums[i];
            int complement = target - num;
            if (mapNum.count(complement) &gt; 0){
                return {mapNum[complement], i};
            } else {
                mapNum.insert({num, i});
            }
        }
        return {-1, -1};
    }
};
</code></pre>
<h3 id="c-2"><a href="#c-2"><span class="icon icon-link"></span></a>C</h3>
<p>Runtime  ms, Memory  MB
<a href="https://leetcode.com/problems/two-sum/solutions/189807/c-c-python-various-solutions-c-is-o-n-time-and-just-6-lines-c-hashmap-solution/?q=c&amp;orderBy=most_votes">Reference</a></p>
<pre><code>struct number_hash {
  int value;
  int index;
  UT_hash_handle hh;
};

void destroy_table(struct number_hash** table) {
  struct number_hash* curr;
  struct number_hash* tmp;

  HASH_ITER(hh, *table, curr, tmp) {
    HASH_DEL(*table, curr);
    free(curr);
  }
}

int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
  struct number_hash* table = NULL;
  struct number_hash* element;
  int* ret = (int*) malloc(2 * sizeof(int));
  int remaining;
  for (int i = 0; i &lt; numsSize; ++i) {
    remaining = target - nums[i];

    // Find if there has already been an element such that the sum is target
    HASH_FIND_INT(table, &amp;remaining, element);
    if (element) {
      ret[0] = element-&gt;index;
      ret[1] = i;
      break;
    }

    // Add the new number to the hash table if it doesn't exist already
    HASH_FIND_INT(table, &amp;nums[i], element);
    if (!element) {
      element = (struct number_hash *) malloc(sizeof(*element));
      element-&gt;value = nums[i];
      element-&gt;index = i;

      HASH_ADD_INT(table, value, element);
    }
  }

  destroy_table(&amp;table);

  *returnSize = 2;
  return ret;
}
</code></pre>]]></content:encoded></item><item><title>R-CNN Series - R-CNN、Fast R-CNN、Faster R-CNN、Mask R-CNN、PointRend</title><link>https://fskuan.com/posts/ai/cnn/rcnn_series/</link><guid isPermaLink="true">https://fskuan.com/posts/ai/cnn/rcnn_series/</guid><pubDate>Thu, 30 Jul 2020 14:31:59 GMT</pubDate><description>Intruduction of R-CNN Series</description><content:encoded><![CDATA[<h1 id="r-cnn系列"><a href="#r-cnn系列"><span class="icon icon-link"></span></a>R-CNN系列</h1>
<p>本篇將會介紹以下論文：</p>
<ol>
<li>R-CNN (2014)</li>
<li>Fast R-CNN (2015)</li>
<li>SPP-Net (2014)</li>
<li>Faster R-CNN (2015)</li>
<li>FPN (2017)</li>
<li>Mask R-CNN (2017)</li>
<li>PointRend (2020)
並且會著重在最後兩篇</li>
</ol>
<p>R-CNN系列主要專注在目標檢測(Object Detection)任務上，如下圖。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/0.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/0.680.avif 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/0.901.avif 901w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/0.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/0.680.webp 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/0.901.webp 901w" type="image/webp"></source><img alt="Object detection examples labeling a car, horse, people, dog, and cat with bounding boxes and confidence scores" class="article-image article-wide" decoding="async" height="340" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/0.png" width="901" /></picture></p>
<h2 id="r-cnn-1--selective-search-2--cnn-alexnet-9--svm"><a href="#r-cnn-1--selective-search-2--cnn-alexnet-9--svm"><span class="icon icon-link"></span></a>R-CNN<sup> [1]</sup> :  Selective Search<sup> [2]</sup> + CNN (AlexNet<sup> [9]</sup>) + SVM</h2>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/1.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/1.680.avif 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/1.960.avif 960w, https://fskuan.com/posts/ai/cnn/rcnn_series/1.1099.avif 1099w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/1.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/1.680.webp 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/1.960.webp 960w, https://fskuan.com/posts/ai/cnn/rcnn_series/1.1099.webp 1099w" type="image/webp"></source><img alt="R-CNN pipeline from region proposals through warped regions, CNN features, and per-region classification" class="article-image article-wide" decoding="async" height="360" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/1.png" width="1099" /></picture></p>
<p></p><figcaption>圖1<sup> [1]</sup></figcaption><p></p>
<p>R-CNN架構:</p>
<ol>
<li>
<p>輸入一個圖片</p>
</li>
<li>
<p>將圖片透過Selective Search提取出2000個候選框(Region Proposals)</p>
</li>
<li>
<p>將每個AlexNet<sup> [9]</sup></p>
</li>
<li>
<p>將每個候選框(Region Proposals)送入CNN模型進行特徵提取</p>
</li>
<li>
<p>每個候選框(Region Proposals)都會有一組經過CNN模型所提取的特徵(features)，再將特徵用SVM來進行該候選框的物體分類</p>
<p>候選框(Region Proposals)的特徵(features)上，使用GPU的情況下，每張圖花費了13秒；使用CPU的情況下，每張圖花費了53秒。</p>
</li>
</ol>
<p>Fast R-CNN<sup> [3]</sup> ：Selective Search<sup> [2]</sup> + RoI + CNN
R-CNN擁有以下缺點:
訓練過程是多個階段進行: R-CNN的訓練過程分為三個階段微調(fine-tune)CNN模型、用於分類的SVM以及Bounding box Regression。
訓練太耗時且需要大量的存儲空間:為了訓練SVM以及Bounding box Regression，需要將每個圖中的每個物體候選框(Object Proposals)經過CNN模型提取特徵後，再將提取的特徵存入硬碟中。對於非常深的CNN模型來說，這個過程需要2.5天才能處理完VOC07訓練集中的5000筆圖像資料且需要數百GB的存儲空間。
物件偵測(Object Detection)太慢:在測試時，將從每個測試圖像中的每個物體候選框(Object Proposals)中提取特徵。使用VGG16在GPU上進行檢測每張圖需要47秒。</p>
<p>目前的整體架構：</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/2.404.avif 404w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/2.404.webp 404w" type="image/webp"></source><img alt="R-CNN flow from selective-search proposals and feature extraction to bounding-box regression and softmax" class="article-image" decoding="async" height="640" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/2.png" width="404" /></picture></p>
<p>圖2</p>
<p>Selective Search從圖片提取出2000個候選框(Region Proposals)，再將整張圖送入CNN模型進行特徵圖提取，然後將候選框透過RoI Projection投射到特徵圖上直接取得候選框對應的特徵圖，省去了將每個候選框送至CNN模型進行特徵提取的過程。</p>
<p>目前的整體架構：</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/3.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/3.580.avif 580w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/3.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/3.580.webp 580w" type="image/webp"></source><img alt="Fast R-CNN flow computing a shared feature map before ROI projection, classification, and box regression" class="article-image" decoding="async" height="639" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/3.png" width="580" /></picture></p>
<p>圖3</p>
<p>為了解決這個問題，Fast R-CNN使用到了SPP-Net<sup> [4]</sup>的技術。</p>
<h2 id="fast-r-cnn"><a href="#fast-r-cnn"><span class="icon icon-link"></span></a>Fast R-CNN</h2>
<p>卷積層(Convolution)、池化層(pooling)、全連接層(Fully-Connected)的CNN模型中，模型的架構都是輸入圖經過縮放成固定大小的圖，然後再送入卷積層中計算出特徵圖，再將特徵圖送到全連接層，最後輸出分類結果(如下圖中上方的流程)，而縮放/型變的過程多少都會損失圖像特徵；而SPP-Net的架構是在卷積層與全連接層間加入了SPP層，使得不固定大小的輸入圖可以在經過SPP層後擁有固定大小的特徵圖。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/4.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/4.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/4.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/4.640.webp 640w" type="image/webp"></source><img alt="Comparison of crop and warp preprocessing with a spatial pyramid pooling network" class="article-image" decoding="async" height="375" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/4.png" width="640" /></picture></p>
<p>(1+4+16)x256的特徵圖。簡單來說，SPP layers是一個會依照輸入特徵圖的大小來調整max pooling參數使得輸出特徵擁有固定大小的結構。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/6.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/6.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/6.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/6.640.webp 640w" type="image/webp"></source><img alt="Spatial pyramid pooling configuration combining 3 by 3, 2 by 2, and 1 by 1 max-pooling outputs into a fully connected layer" class="article-image" decoding="async" height="428" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/6.png" width="640" /></picture></p>
<p>圖6<sup> [4]</sup></p>
<p>h/H x w/W大小的子網格中的值max pooling到對應的H x W網格中(整個流程如圖7所示))</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/7.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/7.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/7.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/7.640.webp 640w" type="image/webp"></source><img alt="ROI pooling example projecting a highlighted feature-map region into a fixed 2 by 2 grid" class="article-image" decoding="async" height="555" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/7.png" width="640" /></picture></p>
<p>完整的Fast R-CNN架構<sup> [3]</sup>:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/8.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/8.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/8.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/8.640.webp 640w" type="image/webp"></source><img alt="Fast R-CNN architecture with a shared convolutional feature map, ROI pooling, softmax classification, and box regression" class="article-image" decoding="async" height="248" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/8.png" width="640" /></picture></p>
<h2 id="faster-r-cnn-5-cnn--rpn--roi"><a href="#faster-r-cnn-5-cnn--rpn--roi"><span class="icon icon-link"></span></a>Faster R-CNN<sup> [5]</sup> ：CNN + RPN + RoI</h2>
<p>儘管Fast R-CNN在各個方面都進行了優化，但是也僅能在忽略使用Selective Search尋找候選框情況下，勉強達到接近實時(near real-time)。</p>
<p>如果在CPU上實現Selective Search的話，每張圖像需要耗費約2秒的時間，而CNN的運算是使用GPU來實現的，Selective Search會造成整個架構在執行速度上的一個瓶頸，所以為了解決這個問題Faster R-CNN採用了一個基於GPU實現的網路模型來取代Selective Search，這個網路模型稱為Region Proposals Networks(RPN)。</p>
<p>但是在捨棄Selective Search的情況下要怎麼獲得候選框呢?
Faster R-CNN採用了anchors的方式來產生候選框，anchors是由三個大小(128、256、512 pixels)以及三種長寬比(1:1、1:2、2:1)所組成的，一共有9個大小、長寬各異的anchors，如圖9所示。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/9.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/9.627.avif 627w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/9.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/9.627.webp 627w" type="image/webp"></source><img alt="Anchor and proposal dimensions for three aspect ratios across 128, 256, and 512 pixel scales" class="article-image" decoding="async" height="40" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/9.png" width="627" /></picture></p>
<p>而anchors是在經由CNN模型計算過後的特徵圖上的每個點為中心都配置上這九個anchors(如圖10)。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/10.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/10.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/10.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/10.640.webp 640w" type="image/webp"></source><img alt="Region proposal network sliding window producing object scores and bounding-box coordinates for k anchors" class="article-image" decoding="async" height="367" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/10.png" width="640" /></picture></p>
<p>60 * 40 * 9 = 21600個anchors (VGG16經過4次pool_size=2, strides=2的max pooling操作，特徵圖的大小會縮小16倍)， 但是其中有的anchors會超過圖像的邊界，如果忽略超過邊界的話，大約有6千個anchors。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/11.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/11.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/11.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/11.640.webp 640w" type="image/webp"></source><img alt="Dense grid of multi-scale anchor boxes covering an image" class="article-image" decoding="async" height="538" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/11.png" width="640" /></picture></p>
<p>介紹完了anchors就可以正式進入RPN的介紹，RPN分為兩個部分:
第一部分(圖12上方部分)為分類層(box-classification layers)，主要是透過softmax來分類anchors，將anchors分為positive和negative。</p>
<p>第二部分(圖12下方部分)為box-regression layers，負責初步的為anchors進行校正。</p>
<p>最後proposals layers負責綜合所有的資訊(positive anchors跟box-regression)來產生候選框</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/12.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/12.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/12.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/12.640.webp 640w" type="image/webp"></source><img alt="Region proposal network branches from a 3 by 3 feature window into softmax scores and proposal coordinates" class="article-image" decoding="async" height="166" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/12.jpg" width="640" /></picture></p>
<p>圖12<sup> [11]</sup></p>
<p>簡單的說，RPN就是在特徵圖上窮舉各個大小的anchors，再利用RPN來判斷positive、negative跟初步的anchors校正以產生出候選框。</p>
<p>Faster R-CNN的整體架構:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/13.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/13.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/13.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/13.640.webp 640w" type="image/webp"></source><img alt="Faster R-CNN network combining a convolutional feature map, region proposals, ROI pooling, classification, and box prediction" class="article-image" decoding="async" height="313" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/13.jpg" width="640" /></picture></p>
<p>圖13<sup> [11]</sup></p>
<p>後面的架構剩RoI Pooling提取proposals的特徵圖然後進行RoI Pooling(Fast R-CNN有提到)，最後輸出分類結果以及進一步的校正anchors。</p>
<p>Faster R-CNN採用了RPN技術達到了73.2% mAP，執行速度相比於使用Selective Search的Fast R-CNN提升了約9倍。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/14.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/14.636.avif 636w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/14.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/14.636.webp 636w" type="image/webp"></source><img alt="Benchmark table comparing selective search and shared RPN proposals by mAP and processing time" class="article-image" decoding="async" height="142" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/14.png" width="636" /></picture></p>
<p>圖14<sup> [5]</sup></p>
<h2 id="mask-r-cnn-7-cnn--rpn--roialign--fpn-6"><a href="#mask-r-cnn-7-cnn--rpn--roialign--fpn-6"><span class="icon icon-link"></span></a>Mask R-CNN<sup> [7]</sup> ：CNN + RPN + RoIAlign + FPN<sup> [6]</sup></h2>
<p>截至目前為止所介紹的R-CNN、Fast R-CNN、Faster R-CNN都是屬於Object Detection的模型，但是R-CNN並不止於此，Mask R-CNN提出了一種概念上簡單、靈活且基於Faster R-CNN的架構，Mask R-CNN在基於Faster R-CNN的基礎上進行了拓展，將原本應用於Object Detection的模型拓展到Instance Segmentation任務上，Instance Segmentation是什麼?</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/15.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/15.500.avif 500w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/15.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/15.500.webp 500w" type="image/webp"></source><img alt="Balloons illustrating classification, semantic segmentation, object detection, and instance segmentation" class="article-image" decoding="async" height="375" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/15.png" width="500" /></picture></p>
<p>圖15</p>
<p>圖15展示了四種不同的圖像視覺任務:
左上角 : Classification，圖像分類，"氣球"
右上角 : Semantic Segmentation，語義分隔，"氣球的像素"
左下角 : Object Detection，目標檢測，主要是框出目標，"框出氣球的位置並且辨識"
右下角 : Instance Segmentation，實例分隔，不僅要框出氣球所在的位置，還要mask出屬於氣球的像素，"框出氣球的位置並且辨識還要mask出屬於氣球的像素"</p>
<p>為了能夠正確的mask出物體的像素，Mask R-CNN對於Faster R-CNN做出了RoI pooling部分的修正。</p>
<p>在Faster R-CNN中所使用的RoI pooling技術是將proposal feature map粗略分割為H x W 的網格並且對每個子網格做max pooling操作(如圖7)，這樣的操作在對於精確度不高的Object Detection任務中足以勝任，但是在Instance Segmentation任務中需要到達像素級別的精確度，因為除了需要框出物體的位置外，還需要將屬於物體的像素點mask出來(如圖15右下角)。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/7.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/7.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/7.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/7.640.webp 640w" type="image/webp"></source><img alt="ROI pooling example projecting a highlighted feature-map region into a fixed 2 by 2 grid" class="article-image" decoding="async" height="555" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/7.png" width="640" /></picture></p>
<p>圖7</p>
<p>在Faster R-CNN中，候選框是由:
RPN中的anchors經過positive和negative的判斷後，再經過regression修正(此時的候選框座標可能有小數)
由於特徵圖中沒有帶有小數的座標點，所以候選框的座標直接取整數(如圖16)(第一次量化)
執行RoI pooling(如圖7)(第二次量化)
經過兩次量化後位置的資訊已經有所偏移，不足以用來精確的mask出物體所屬的像素點。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/16.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/16.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/16.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/16.640.webp 640w" type="image/webp"></source><img alt="ROI Align example preserving fractional sample positions while mapping a feature region to a fixed output grid" class="article-image" decoding="async" height="551" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/16.png" width="640" /></picture></p>
<p>圖16</p>
<p>為了解決這個問題，Mask R-CNN對RoI pooling進行了修改，不再使用取整數的方式來取值。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/17.400.avif 400w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/17.400.webp 400w" type="image/webp"></source><img alt="ROI Align sampling points inside four bins with bilinear interpolation from a surrounding feature grid" class="article-image" decoding="async" height="400" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/17.png" width="400" /></picture></p>
<p>圖17<sup> [7]</sup></p>
<p>為了解決這個問題，Mask R-CNN對RoI pooling進行了修改:
RPN中的anchors經過positive和negative的判斷後，再經過regression修正(此時的候選框座標可能有小數)
不對候選框(RoI)的座標取整數(保持有小數狀態)
將RoI直接平分成H x W個網格(bins)，並且在每個子網格中平均取4個採樣點，每個採樣點的值由周圍最靠近的4個點做雙線性擦值(bilinear interpolation)取得
子網格的值由子網格內的4個採樣點做max/average pooling而得
在RoIAlign中，所有的值都為帶有小數狀態並沒有經過量化。</p>
<p>Mask R-CNN還新增了一個預測mask的head，mask head可以採用FCN或是FPN模型，由於Mask R-CNN採用FPN作為mask head的結果較好，就介紹以FPN當作mask head的架構。</p>
<p>Mask R-CNN整體架構:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/18.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/18.638.avif 638w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/18.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/18.638.webp 638w" type="image/webp"></source><img alt="Mask R-CNN flow from input image through CNN backbone and RPN to classification, box regression, and mask heads" class="article-image" decoding="async" height="320" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/18.png" width="638" /></picture></p>
<p>圖18</p>
<p>FPN<sup> [6]</sup>(如圖19)簡單來說就是利用卷積過程中的特徵圖(features map)來對高維度的特徵圖進行upsampling。</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/19.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/19.640.avif 640w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/19.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/19.640.webp 640w" type="image/webp"></source><img alt="Feature pyramid network passing a bottom-up image pyramid to three prediction scales" class="article-image" decoding="async" height="254" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/19.png" width="640" /></picture></p>
<p>圖19</p>
<p>假設卷積過程中各個階段的特徵圖為C2、C3、C4、C5，FPN的具體操作為:
C5經過Conv1-256後成為P5
將P5進行upsampling後與經過Conv1-256後的C4相加後成為P4
將P4進行upsampling後與經過Conv1-256後的C3相加後成為P3
以此類推</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/20.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/20.639.avif 639w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/20.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/20.639.webp 639w" type="image/webp"></source><img alt="Feature pyramid network with a top-down upsample and lateral 1 by 1 convolution merge" class="article-image" decoding="async" height="540" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 680px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/20.png" width="639" /></picture></p>
<p>圖20</p>
<p>Mask R-CNN 在目標檢測(Object Detection)上的結果:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/21.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/21.680.avif 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/21.960.avif 960w, https://fskuan.com/posts/ai/cnn/rcnn_series/21.1000.avif 1000w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/21.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/21.680.webp 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/21.960.webp 960w, https://fskuan.com/posts/ai/cnn/rcnn_series/21.1000.webp 1000w" type="image/webp"></source><img alt="COCO object-detection benchmark table comparing Faster R-CNN and Mask R-CNN backbones" class="article-image article-wide" decoding="async" height="242" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/21.png" width="1000" /></picture></p>
<p>圖21</p>
<p>Mask R-CNN 在實例分割(Instance Segmentation)上的結果:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/22.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/22.680.avif 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/22.960.avif 960w, https://fskuan.com/posts/ai/cnn/rcnn_series/22.1000.avif 1000w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/22.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/22.680.webp 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/22.960.webp 960w, https://fskuan.com/posts/ai/cnn/rcnn_series/22.1000.webp 1000w" type="image/webp"></source><img alt="COCO instance-segmentation benchmark table comparing MNC, FCIS, and Mask R-CNN backbones" class="article-image article-wide" decoding="async" height="224" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/22.png" width="1000" /></picture></p>
<p>圖22</p>
<p>Mask R-CNN結果:</p>
<p><picture><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/23.480.avif 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/23.680.avif 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/23.960.avif 960w, https://fskuan.com/posts/ai/cnn/rcnn_series/23.1273.avif 1273w" type="image/avif"></source><source sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" srcset="https://fskuan.com/posts/ai/cnn/rcnn_series/23.480.webp 480w, https://fskuan.com/posts/ai/cnn/rcnn_series/23.680.webp 680w, https://fskuan.com/posts/ai/cnn/rcnn_series/23.960.webp 960w, https://fskuan.com/posts/ai/cnn/rcnn_series/23.1273.webp 1273w" type="image/webp"></source><img alt="Eight Mask R-CNN examples with colored instance masks and labeled bounding boxes" class="article-image article-wide" decoding="async" height="482" loading="lazy" sizes="(max-width: 42rem) calc(100vw - 2rem), 960px" src="https://fskuan.com/posts/ai/cnn/rcnn_series/23.png" width="1273" /></picture></p>
<h2 id="實作implementation"><a href="#實作implementation"><span class="icon icon-link"></span></a>實作(Implementation)</h2>
<p>實作的部分採用matterport/Mask_RCNN<sup> [12]</sup>展示，由於論文原文<sup> [7]</sup>中所公開的實作專案<sup> [13]</sup>是採用PyTorch並且在Linux或MacOS平台上所實作的，與我平常習慣的操作系統和深度學習架構不同所以優先介紹matterport/Mask_RCNN<sup> [12]</sup>，未來有機會再補上Linux、PyTorch版本的。</p>
<p>matterport/Mask_RCNN</p>
<ul>
<li>Python 3.4</li>
<li>TensorFlow 1.3</li>
<li>Keras 2.0.8</li>
<li>其他library列在requirements.txt中</li>
</ul>
<p>在以下環境中成功執行(2020/07/28):</p>
<ul>
<li>Python 3.7.5</li>
<li>TensorFlow 1.15</li>
<li>Keras 2.1.3</li>
<li>其他library列在requirements.txt中</li>
<li></li>
</ul>
<p>安裝&amp;測試執行流程:</p>
<ol>
<li>git clone <a href="https://github.com/matterport/Mask_RCNN.git">https://github.com/matterport/Mask_RCNN.git</a></li>
<li>安裝相關library
<pre><code>pip install -r requirements.txt
</code></pre>
</li>
<li>執行setup
<pre><code>python setup.py install
</code></pre>
</li>
<li>從Github上下載test_DEMO.py並且將test_DEMO.py放入專案中
<pre><code>git clone https://gist.github.com/ghit42796/65965f82b1ada3b4cb47010f95323a42
</code></pre>
</li>
<li>執行test_DEMO.py
<pre><code>python test_DEMO.py
</code></pre>
</li>
</ol>
<p>訓練部分(只在TensorFlow 1中測試過)，從Github上下載train_DEMO.py並且將test_DEMO.py放入專案中:</p>
<pre><code>git clone https://gist.github.com/ghit42796/c5abdf0da07b2be192ad59315251b37c
</code></pre>
<p>在TensorFlow2也可以執行只需要將repository改成akTwelve/Mask_RCNN<sup> [14]</sup></p>
<pre><code>git clone https://github.com/akTwelve/Mask_RCNN
</code></pre>
<p>其他操作同上</p>
<p>PointRend<sup> [8]</sup> ：
TO BE CONTINUED!!</p>
<h2 id="reference"><a href="#reference"><span class="icon icon-link"></span></a>Reference：</h2>
<ol>
<li>R. Girshick, J.Donahue, T.Darrell, and J.Malik. Rich feature hierarchies for accurate object detection and semantic segmentation. In CVPR, 2014.</li>
<li>J. Uijlings, K. van de Sande, T. Gevers, and A. Smeulders. Selective search for object recognition. IJCV, 2013.</li>
<li>R. Girshick. Fast R-CNN. In ICCV, 2015.</li>
<li>K. He, X. Zhang, S. Ren, and J. Sun. Spatial pyramid pooling in deep convolutional networks for visual recognition. In ECCV. 2014.</li>
<li>S. Ren, K. He, R. Girshick, and J. Sun. Faster R-CNN: Towards real-time object detection with region proposal networks. In NIPS, 2015.</li>
<li>T.-Y. Lin, P. Doll´ar, R. Girshick, K. He, B. Hariharan, and S. Belongie. Feature pyramid networks for object detection. In CVPR, 2017.</li>
<li>Kaiming He, Georgia Gkioxari, Piotr Doll´ar, and Ross Girshick. Mask R-CNN. In ICCV, 2017.</li>
<li>A. Kirillov, Y. Wu, K. He, and R. Girshick, “Pointrend: Image segmentation as rendering,” in IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2020.</li>
<li>A. Krizhevsky, I. Sutskever, and G. Hinton. Imagenet classification with deep convolutional neural networks. In NIPS, 2012.</li>
<li>K. Simonyan and A. Zisserman. Very deep convolutional networks for large-scale image recognition. In ICLR, 2015.</li>
<li><a href="https://zhuanlan.zhihu.com/p/31426458">一文读懂Faster RCNN</a></li>
<li><a href="https://github.com/matterport/Mask_RCNN">https://github.com/matterport/Mask_RCNN</a></li>
<li><a href="https://github.com/facebookresearch/detectron2">https://github.com/facebookresearch/detectron2</a></li>
<li><a href="https://github.com/akTwelve/Mask_RCNN">https://github.com/akTwelve/Mask_RCNN</a></li>
</ol>]]></content:encoded></item></channel></rss>