1. 總覽
在第一個程式碼研究室中,您會將圖片上傳至 bucket。這會產生檔案建立事件,並由函式處理。函式會呼叫 Vision API 進行圖片分析,並將結果儲存在資料存放區。

課程內容
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore
2. 設定和需求
自修實驗室環境設定
- 登入 Google Cloud 控制台,然後建立新專案或重複使用現有專案。如果沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶。



- 專案名稱是這個專案參與者的顯示名稱。這是 Google API 未使用的字元字串。你隨時可以更新該位置資訊。
- 專案 ID 在所有 Google Cloud 專案中不得重複,且設定後即無法變更。Cloud 控制台會自動產生不重複的字串,通常您不需要在意這個字串。在大多數程式碼研究室中,您需要參照專案 ID (通常會標示為
PROJECT_ID)。如果您不喜歡產生的 ID,可以產生另一個隨機 ID。你也可以嘗試自訂名稱,看看是否可用。完成這個步驟後就無法變更,且專案期間都會維持這個設定。 - 請注意,部分 API 會使用第三個值,也就是「專案編號」。如要進一步瞭解這三種值,請參閱說明文件。
- 接著,您需要在 Cloud 控制台中啟用帳單,才能使用 Cloud 資源/API。完成本程式碼研究室的費用應該不高,甚至完全免費。如要關閉資源,避免產生本教學課程以外的費用,您可以刪除自己建立的資源,或刪除整個專案。Google Cloud 新使用者可參加價值$300 美元的免費試用計畫。
啟動 Cloud Shell
雖然可以透過筆電遠端操作 Google Cloud,但在本程式碼研究室中,您將使用 Google Cloud Shell,這是可在雲端執行的指令列環境。
在 Google Cloud 控制台中,點選右上工具列的 Cloud Shell 圖示:

佈建並連線至環境的作業需要一些時間才能完成。完成後,您應該會看到如下的內容:

這部虛擬機器搭載各種您需要的開發工具,並提供永久的 5GB 主目錄,而且可在 Google Cloud 運作,大幅提升網路效能並強化驗證功能。您可以在瀏覽器中完成本程式碼研究室的所有作業。您不需要安裝任何軟體。
3. 啟用 API
在本實驗室中,您將使用 Cloud Functions 和 Vision API,但首先必須在 Cloud 控制台或使用 gcloud 啟用這些服務。
如要在 Cloud 控制台中啟用 Vision API,請在搜尋列中搜尋 Cloud Vision API:

系統會將您導向 Cloud Vision API 頁面:

按一下 ENABLE 按鈕。
或者,您也可以使用 gcloud 指令列工具,透過 Cloud Shell 啟用。
在 Cloud Shell 中執行下列指令:
gcloud services enable vision.googleapis.com
您應該會看到作業順利完成:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
同時啟用 Cloud Functions:
gcloud services enable cloudfunctions.googleapis.com
4. 建立 bucket (主控台)
建立圖片的儲存空間值區。您可以透過 Google Cloud Platform 主控台 ( console.cloud.google.com) 或 Cloud Shell 或本機開發環境的 gsutil 指令列工具執行這項操作。
前往「儲存空間」
在「漢堡」選單 (☰) 中,前往 Storage 頁面。

為 bucket 命名
按一下 CREATE BUCKET 按鈕。

按一下「CONTINUE」。
選擇位置

在所選區域 (此處為 Europe) 建立多區域 bucket。
按一下「CONTINUE」。
選擇預設儲存空間級別

為資料選擇 Standard 儲存空間級別。
按一下「CONTINUE」。
設定存取權控管

由於您將使用可公開存取的圖片,因此希望儲存在這個 bucket 中的所有圖片都具有相同的統一存取權控管機制。
選擇 Uniform 存取權控管選項。
按一下「CONTINUE」。
設定保護/加密

保留預設值 (Google-managed key)),因為您不會使用自己的加密金鑰。
按一下 CREATE,最終完成值區建立程序。
將 allUsers 新增為儲存空間檢視者
前往 Permissions 分頁:

將 allUsers 成員新增至 bucket,並指派 Storage > Storage Object Viewer 角色,如下所示:

按一下「SAVE」。
5. 建立 bucket (gsutil)
您也可以使用 Cloud Shell 中的 gsutil 指令列工具建立 bucket。
在 Cloud Shell 中,為不重複的值區名稱設定變數。Cloud Shell 已將 GOOGLE_CLOUD_PROJECT 設為專屬專案 ID。你可以將該值附加至 bucket 名稱。
例如:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
在歐洲建立標準多區域:
gsutil mb -l EU gs://${BUCKET_PICTURES}
確認統一值區層級存取權:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
將 bucket 設為公開:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
前往控制台的 Cloud Storage 部分,您應該會看到公開 uploaded-pictures bucket:

如上一個步驟所述,測試您是否可以將圖片上傳至值區,並確認上傳的圖片可公開存取。
6. 測試 bucket 的公開存取權
返回儲存空間瀏覽器,您會在清單中看到自己的 bucket,並顯示「公開」存取權 (包括提醒您任何人都能存取該 bucket 內容的警告符號)。

現在值區已可接收圖片。
按一下 bucket 名稱,即可查看 bucket 詳細資料。

您可以在該處嘗試 Upload files 按鈕,測試是否能將圖片新增至值區。檔案選擇器彈出式視窗會要求你選取檔案。選取後,系統會將檔案上傳至儲存空間,並再次顯示自動指派給這個新檔案的public存取權。

Public 存取權標籤旁邊也會顯示小小的連結圖示。點選後,瀏覽器會前往該圖片的公開網址,格式如下:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
其中 BUCKET_NAME 是您為 bucket 選擇的全域專屬名稱,後面則是圖片的檔案名稱。
按一下圖片名稱旁的核取方塊,即可啟用 DELETE 按鈕,並刪除第一張圖片。
7. 建立函式
在這個步驟中,您會建立函式來回應圖片上傳事件。
前往 Google Cloud 控制台的「Cloud Functions」專區。只要前往該頁面,系統就會自動啟用 Cloud Functions 服務。

按一下 Create function。
選擇名稱 (例如 picture-uploaded),以及「Region」(區域) (請務必與 bucket 的區域選擇保持一致):

函式分為兩種:
- 可透過網址叫用的 HTTP 函式 (即 Web API)。
- 可由某些事件觸發的背景函式。
您想建立背景函式,在上傳新檔案至 Cloud Storage 值區時觸發:

您感興趣的事件類型是 Finalize/Create,也就是在 Bucket 中建立或更新檔案時觸發的事件:

選取先前建立的 bucket,讓 Cloud Functions 在這個 bucket 中建立 / 更新檔案時收到通知:

按一下 Select 選擇先前建立的 bucket,然後按一下 Save

按一下「下一步」前,您可以展開並修改「執行階段、建構作業、連線和安全性設定」下方的預設值 (256 MB 記憶體),然後更新為 1 GB。

點選 Next 後,即可調整「執行階段」、「原始碼」和「進入點」。
保留這個函式的 Inline editor:

選取其中一個 Node.js 執行階段:

原始碼包含 index.js JavaScript 檔案,以及提供各種中繼資料和依附元件的 package.json 檔案。
保留預設的程式碼片段,這個片段會記錄上傳圖片的檔案名稱:

目前,請將要執行的函式名稱保留為 helloGCS,以利測試。
按一下 Deploy 即可建立及部署函式。部署成功後,函式清單中會顯示綠色圓圈勾號:

8. 測試函式
在本步驟中,請測試函式是否會回應儲存空間事件。
從「漢堡」選單 (☰) 返回 Storage 頁面。
按一下圖片值區,然後按一下 Upload files 上傳圖片。

再次前往 Cloud 控制台中的 Logging > Logs Explorer 頁面。
在 Log Fields 選取器中,選取 Cloud Function 即可查看函式專屬記錄。向下捲動瀏覽記錄檔欄位,您甚至可以選取特定函式,更精細地查看函式相關記錄檔。選取 picture-uploaded 函式。
您應該會看到記錄項目,其中提到函式的建立時間、函式的開始和結束時間,以及實際的記錄陳述式:

記錄陳述式為 Processing file: pic-a-daily-architecture-events.png,表示與建立及儲存這張相片相關的事件確實已如預期觸發。
9. 準備資料庫
您會將 Vision API 提供的圖片資訊儲存到 Cloud Firestore 資料庫。這項服務是快速、全代管、無伺服器且雲端原生的 NoSQL 文件資料庫。前往 Cloud 控制台的「Firestore」部分,準備資料庫:

系統會提供兩個選項:Native mode 或 Datastore mode。使用原生模式,即可享有離線支援和即時同步等額外功能。
按一下 SELECT NATIVE MODE。

選擇多地區 (這裡選擇歐洲,但最好與函式和儲存空間值區位於相同地區)。
按一下 CREATE DATABASE 按鈕。
資料庫建立完成後,您應該會看到下列內容:

按一下 + START COLLECTION 按鈕,建立新的集合。
為集合命名 pictures。

您不需要建立文件,當新圖片儲存在 Cloud Storage 中,並由 Vision API 分析時,您會以程式輔助方式新增圖片。
按一下「Save」。
Firestore 會在新建立的集合中建立第一個預設文件,您可以安全地刪除該文件,因為其中不含任何實用資訊:

我們將在集合中以程式輔助方式建立的文件會包含 4 個欄位:
- name (字串):上傳圖片的檔案名稱,也是文件的鍵
- 標籤 (字串陣列):Vision API 辨識項目的標籤
- color (字串):主色的十六進位顏色代碼 (即 #ab12ef)
- created (日期):儲存這張圖片中繼資料的時間戳記
- 縮圖 (布林值):選用欄位,如果系統已為這張相片產生縮圖,這個欄位就會存在並設為 true
由於我們會在 Firestore 中搜尋有縮圖的圖片,並依建立日期排序,因此需要建立搜尋索引。
您可以在 Cloud Shell 中使用下列指令建立索引:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
您也可以在 Cloud 控制台中執行這項操作,方法是點選左側導覽欄中的 Indexes,然後建立複合索引,如下所示:

按一下 Create,建立索引可能需要幾分鐘的時間。
10. 更新函式
返回 Functions 頁面,更新函式以呼叫 Vision API 分析圖片,並將中繼資料儲存在 Firestore 中。
從「漢堡」選單 (☰) 前往 Cloud Functions 專區,按一下函式名稱,選取「Source」分頁標籤,然後按一下「EDIT」按鈕。
首先,請編輯 package.json 檔案,其中列出 Node.JS 函式的依附元件。更新程式碼,加入 Cloud Vision API NPM 依附元件:
{
"name": "picture-analysis-function",
"version": "0.0.1",
"dependencies": {
"@google-cloud/storage": "^1.6.0",
"@google-cloud/vision": "^1.8.0",
"@google-cloud/firestore": "^3.4.1"
}
}
依附元件更新完畢後,您將更新 index.js 檔案,處理函式的程式碼。
請將 index.js 中的程式碼替換成下方程式碼。我們會在下一個步驟中說明。
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
const client = new vision.ImageAnnotatorClient();
exports.vision_analysis = async (event, context) => {
console.log(`Event: ${JSON.stringify(event)}`);
const filename = event.name;
const filebucket = event.bucket;
console.log(`New picture uploaded ${filename} in ${filebucket}`);
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
// invoking the Vision API
const [response] = await client.annotateImage(request);
console.log(`Raw vision output for: ${filename}: ${JSON.stringify(response)}`);
if (response.error === null) {
// listing the labels found in the picture
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
console.log(`Labels: ${labels.join(', ')}`);
// retrieving the dominant color of the picture
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
console.log(`Colors: ${colorHex}`);
// determining if the picture is safe to show
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"].every(k =>
!['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
console.log(`Safe? ${isSafe}`);
// if the picture is safe to display, store it in Firestore
if (isSafe) {
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(filename);
await doc.set({
labels: labels,
color: colorHex,
created: Firestore.Timestamp.now()
}, {merge: true});
console.log("Stored metadata in Firestore");
}
} else {
throw new Error(`Vision API error: code ${response.error.code}, message: "${response.error.message}"`);
}
};
function decColorToHex(r, g, b) {
return '#' + Number(r).toString(16).padStart(2, '0') +
Number(g).toString(16).padStart(2, '0') +
Number(b).toString(16).padStart(2, '0');
}
11. 探索函式
讓我們進一步瞭解各個有趣的部分。
首先,我們需要 Vision、Storage 和 Firestore 的必要模組:
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
接著,我們準備 Vision API 的用戶端:
const client = new vision.ImageAnnotatorClient();
現在來看看函式的結構。我們將其設為非同步函式,因為我們使用 Node.js 8 中導入的 async / await 功能:
exports.vision_analysis = async (event, context) => {
...
const filename = event.name;
const filebucket = event.bucket;
...
}
請注意簽章,以及我們如何擷取觸發 Cloud 函式的檔案和 bucket 名稱。
如要參考,以下是事件酬載的樣子:
{
"bucket":"uploaded-pictures",
"contentType":"image/png",
"crc32c":"efhgyA==",
"etag":"CKqB956MmucCEAE=",
"generation":"1579795336773802",
"id":"uploaded-pictures/Screenshot.png/1579795336773802",
"kind":"storage#object",
"md5Hash":"PN8Hukfrt6C7IyhZ8d3gfQ==",
"mediaLink":"https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/Screenshot.png?generation=1579795336773802&alt=media",
"metageneration":"1",
"name":"Screenshot.png",
"selfLink":"https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/Screenshot.png",
"size":"173557",
"storageClass":"STANDARD",
"timeCreated":"2020-01-23T16:02:16.773Z",
"timeStorageClassUpdated":"2020-01-23T16:02:16.773Z",
"updated":"2020-01-23T16:02:16.773Z"
}
我們準備透過 Vision 用戶端傳送的要求:
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
我們要求 Vision API 提供 3 項主要功能:
- 標籤偵測:瞭解圖片內容
- 圖片屬性:提供圖片的有趣屬性 (我們感興趣的是圖片的主色)
- 安全搜尋:判斷圖片是否適合顯示 (不應含有成人 / 醫療 / 煽情 / 暴力內容)
此時,我們可以呼叫 Vision API:
const [response] = await client.annotateImage(request);
以下是 Vision API 的回應範例:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
如果沒有傳回任何錯誤,我們就可以繼續,這就是我們有這個 if 區塊的原因:
if (response.error === null) {
...
} else {
throw new Error(`Vision API error: code ${response.error.code},
message: "${response.error.message}"`);
}
我們要取得圖片中辨識到的事物、類別或主題的標籤:
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
系統會先顯示分數最高的標籤。
我們想知道圖片的主色:
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
我們再次依分數排序顏色,並取第一個顏色。
我們也使用公用程式函式,將紅 / 綠 / 藍值轉換為十六進位顏色代碼,以便在 CSS 樣式表中使用。
讓我們檢查圖片是否安全無虞:
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"]
.every(k => !['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
我們會檢查成人 / 惡搞 / 醫療 / 暴力 / 煽情屬性,判斷這些屬性「可能」或「非常可能」不適用。
如果安全搜尋結果沒問題,我們可以在 Firestore 中儲存中繼資料:
if (isSafe) {
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(filename);
await doc.set({
labels: labels,
color: colorHex,
created: Firestore.Timestamp.now()
}, {merge: true});
}
12. 部署函式
現在要部署函式。

按下 DEPLOY 按鈕,系統就會部署新版本,您可以查看進度:

13. 再次測試函式
成功部署函式後,請將圖片發布至 Cloud Storage,看看函式是否會遭到叫用、Vision API 會傳回什麼內容,以及中繼資料是否儲存在 Firestore 中。
返回 Cloud Storage,然後按一下我們在實驗室開始時建立的 bucket:

進入值區詳細資料頁面後,按一下 Upload files 按鈕即可上傳圖片。

從「漢堡」選單 (☰) 導覽至「Explorer」Logging > Logs。
在 Log Fields 選取器中,選取 Cloud Function 即可查看函式專屬記錄。向下捲動瀏覽記錄檔欄位,您甚至可以選取特定函式,更精細地查看函式相關記錄檔。選取 picture-uploaded 函式。

在記錄清單中,確實可以看到函式已叫用:

記錄會顯示函式執行的開始和結束時間。中間則會顯示我們在函式中透過 console.log() 陳述式放置的記錄。我們看到:
- 觸發函式的事件詳細資料,
- Vision API 呼叫的原始結果,
- 我們在您上傳的圖片中找到的標籤,
- 主要顏色資訊,
- 圖片是否適合顯示
- 最終,這些圖片中繼資料會儲存在 Firestore 中。

再次從「漢堡」選單 (☰) 前往 Firestore 部分。在 Data 子專區 (預設顯示),您應該會看到 pictures 集合新增的文件,對應您剛上傳的圖片:

14. 清除 (選用)
如果您不打算繼續進行本系列的其他實驗室,可以清除資源來節省費用,並成為優質的雲端使用者。您可以按照下列方式個別清除資源。
刪除值區:
gsutil rb gs://${BUCKET_PICTURES}
刪除函式:
gcloud functions delete picture-uploaded --region europe-west1 -q
從集合中選取「刪除集合」,即可刪除 Firestore 集合:

或者,您也可以刪除整個專案:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. 恭喜!
恭喜!您已成功實作專案的第一項重要服務!
涵蓋內容
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore