Günlük Pic: Laboratuvar 1: Resimleri depolama ve analiz etme (Java)

1. Genel Bakış

İlk kod laboratuvarında, bir pakete resim yükleyeceksiniz. Bu işlem, bir işlev tarafından işlenecek bir dosya oluşturma etkinliği oluşturur. Bu işlev, görüntü analizi yapmak için Vision API'ye bir çağrı yapar ve sonuçları bir veri deposuna kaydeder.

d650ca5386ea71ad.png

Neler öğreneceksiniz?

  • Cloud Storage
  • Cloud Functions
  • Cloud Vision API
  • Cloud Firestore

2. Kurulum ve Gereksinimler

Yönlendirmesiz ortam kurulumu

  1. Google Cloud Console'da oturum açın ve yeni bir proje oluşturun veya mevcut bir projeyi yeniden kullanın. Gmail veya Google Workspace hesabınız yoksa hesap oluşturmanız gerekir.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Proje adı, bu projenin katılımcıları için görünen addır. Google API'leri tarafından kullanılmayan bir karakter dizesidir. Dilediğiniz zaman bunu güncelleyebilirsiniz.
  • Proje kimliği, tüm Google Cloud projelerinde benzersiz olmalı ve sabittir (ayarlandıktan sonra değiştirilemez). Cloud Console, benzersiz bir dizeyi otomatik olarak oluşturur. Genellikle bu dizenin ne olduğuyla ilgilenmezsiniz. Çoğu codelab'de proje kimliğine (genellikle PROJECT_ID olarak tanımlanır) başvurmanız gerekir. Oluşturulan kimliği beğenmezseniz başka bir rastgele kimlik oluşturabilirsiniz. Dilerseniz kendi adınızı deneyerek kullanılabilir olup olmadığını kontrol edebilirsiniz. Bu adımdan sonra değiştirilemez ve proje süresince geçerli kalır.
  • Bazı API'lerin kullandığı üçüncü bir değer olan Proje Numarası da vardır. Bu üç değer hakkında daha fazla bilgiyi belgelerde bulabilirsiniz.
  1. Ardından, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i tamamlamak neredeyse hiç maliyetli değildir. Bu eğitimin ötesinde faturalandırma ücreti alınmaması için kaynakları kapatmak üzere oluşturduğunuz kaynakları veya projenin tamamını silebilirsiniz. Google Cloud'un yeni kullanıcıları 300 ABD doları değerinde ücretsiz deneme programından yararlanabilir.

Cloud Shell'i başlatma

Google Cloud, dizüstü bilgisayarınızdan uzaktan çalıştırılabilir. Ancak bu codelab'de, Cloud'da çalışan bir komut satırı ortamı olan Google Cloud Shell'i kullanacaksınız.

Google Cloud Console'da sağ üstteki araç çubuğunda Cloud Shell simgesini tıklayın:

55efc1aaa7a4d3ad.png

Ortamın temel hazırlığı ve bağlanması yalnızca birkaç dakikanızı alır. İşlem tamamlandığında aşağıdakine benzer bir sonuç görürsünüz:

7ffe5cbb04455448.png

Bu sanal makine, ihtiyaç duyacağınız tüm geliştirme araçlarını içerir. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud üzerinde çalışır. Bu sayede ağ performansı ve kimlik doğrulama önemli ölçüde güçlenir. Bu codelab'deki tüm çalışmalarınızı tarayıcıda yapabilirsiniz. Herhangi bir şey yüklemeniz gerekmez.

3. API'leri etkinleştir

Bu laboratuvarda Cloud Functions ve Vision API'yi kullanacaksınız ancak önce bunların Cloud Console'da veya gcloud ile etkinleştirilmesi gerekir.

Cloud Console'da Vision API'yi etkinleştirmek için arama çubuğunda Cloud Vision API simgesini arayın:

cf48b1747ba6a6fb.png

Cloud Vision API sayfasına yönlendirilirsiniz:

ba4af419e6086fbb.png

ENABLE düğmesini tıklayın.

Alternatif olarak, gcloud komut satırı aracını kullanarak Cloud Shell'de de etkinleştirebilirsiniz.

Cloud Shell'de aşağıdaki komutu çalıştırın:

gcloud services enable vision.googleapis.com

İşlemin başarıyla tamamlandığını görmeniz gerekir:

Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.

Cloud Functions'ı da etkinleştirin:

gcloud services enable cloudfunctions.googleapis.com

4. Paketi oluşturma (konsol)

Resimler için bir depolama paketi oluşturun. Bu işlemi Google Cloud Platform Console'dan ( console.cloud.google.com) veya Cloud Shell ya da yerel geliştirme ortamınızdan gsutil komut satırı aracıyla yapabilirsiniz.

"Hamburger" (☰) menüsünden Storage sayfasına gidin.

1930e055d138150a.png

Paketinizi adlandırma

CREATE BUCKET düğmesini tıklayın.

34147939358517f8.png

CONTINUE simgesini tıklayın.

Konum seçme

197817f20be07678.png

Seçtiğiniz bölgede (burada Europe) çok bölgeli bir paket oluşturun.

CONTINUE simgesini tıklayın.

Varsayılan depolama sınıfını seçme

53cd91441c8caf0e.png

Verileriniz için Standard depolama sınıfını seçin.

CONTINUE simgesini tıklayın.

Erişim denetimini ayarlama

8c2b3b459d934a51.png

Herkese açık olarak erişilebilen resimlerle çalışacağınız için bu pakette depolanan tüm resimlerimizin aynı tek tip erişim denetimine sahip olmasını istiyorsunuz.

Uniform erişim denetimi seçeneğini belirleyin.

CONTINUE simgesini tıklayın.

Koruma/Şifreleme Ayarlama

d931c24c3e705a68.png

Kendi şifreleme anahtarlarınızı kullanmayacağınız için varsayılanı (Google-managed key)) koruyun.

Paket oluşturma işlemini tamamlamak için CREATE simgesini tıklayın.

allUsers'ı depolama alanı görüntüleyeni olarak ekleme

Permissions sekmesine gidin:

d0ecfdcff730ea51.png

Kovaya aşağıdaki gibi allUsers rolüne sahip bir Storage > Storage Object Viewer üyesi ekleyin:

e9f25ec1ea0b6cc6.png

SAVE simgesini tıklayın.

5. Paketi oluşturma (gsutil)

Paket oluşturmak için Cloud Shell'deki gsutil komut satırı aracını da kullanabilirsiniz.

Cloud Shell'de benzersiz paket adı için bir değişken ayarlayın. Cloud Shell'de GOOGLE_CLOUD_PROJECT, benzersiz proje kimliğinize ayarlanmıştır. Bunu paket adına ekleyebilirsiniz.

Örneğin:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

Avrupa'da standart bir çok bölgeli bölge oluşturun:

gsutil mb -l EU gs://${BUCKET_PICTURES}

Tek tip paket düzeyinde erişim sağlama:

gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}

Paketi herkese açık hale getirin:

gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}

Konsolun Cloud Storage bölümüne giderseniz herkese açık bir uploaded-pictures paketi görmeniz gerekir:

a98ed4ba17873e40.png

Önceki adımda açıklandığı gibi, pakete resim yükleyebildiğinizi ve yüklenen resimlerin herkese açık olduğunu test edin.

6. Pakete herkese açık erişimi test etme

Depolama tarayıcısına geri döndüğünüzde, listenizde "Herkese açık" erişim izniyle (herkesin bu paketin içeriğine erişebileceğini hatırlatan bir uyarı işaretiyle birlikte) paketinizi görürsünüz.

89e7a4d2c80a0319.png

Paketiniz artık resim almaya hazır.

Paket adını tıkladığınızda paket ayrıntılarını görürsünüz.

131387f12d3eb2d3.png

Burada, pakete resim ekleyebildiğinizi test etmek için Upload files düğmesini deneyebilirsiniz. Dosya seçici pop-up penceresinde bir dosya seçmeniz istenir. Seçilen dosya, bucket'ınıza yüklenir ve bu yeni dosyaya otomatik olarak atanan public erişimini tekrar görürsünüz.

e87584471a6e9c6d.png

Public erişim etiketinin yanında küçük bir bağlantı simgesi de görürsünüz. Bu URL tıklandığında tarayıcınız, şu biçimde olan resmin herkese açık URL'sine yönlendirilir:

https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png

BUCKET_NAME, paketiniz için seçtiğiniz global olarak benzersiz ad, ardından resminizin dosya adı olmalıdır.

Resim adının yanındaki onay kutusunu tıkladığınızda DELETE düğmesi etkinleştirilir ve bu ilk resmi silebilirsiniz.

7. İşlevi oluşturma

Bu adımda, resim yükleme etkinliklerine tepki veren bir işlev oluşturacaksınız.

Google Cloud Console'un Cloud Functions bölümünü ziyaret edin. Bu sayfayı ziyaret ettiğinizde Cloud Functions hizmeti otomatik olarak etkinleştirilir.

9d29e8c026a7a53f.png

Create function simgesini tıklayın.

Bir ad seçin (ör. picture-uploaded) ve Bölge (paket için bölge seçiminde tutarlı olmayı unutmayın):

4bb222633e6f278.png

İki tür işlev vardır:

  • URL (ör. web API'si) üzerinden başlatılabilen HTTP işlevleri,
  • Bazı etkinlikler tarafından tetiklenebilen arka plan işlevleri.

Cloud Storage paketimize yeni bir dosya yüklendiğinde tetiklenen bir arka plan işlevi oluşturmak istiyorsunuz:

d9a12fcf58f4813c.png

Dosya, pakette oluşturulduğunda veya güncellendiğinde tetiklenen etkinlik olan Finalize/Create etkinlik türüyle ilgileniyorsunuz:

b30c8859b07dc4cb.png

Cloud Functions'a belirli bir pakette dosya oluşturulduğunda / güncellendiğinde bildirim göndermesini söylemek için daha önce oluşturulan paketi seçin:

cb15a1f4c7a1ca5f.png

Daha önce oluşturduğunuz paketi seçmek için Select, ardından Save simgesini tıklayın.

c1933777fac32c6a.png

Sonraki'yi tıklamadan önce Çalışma zamanı, derleme, bağlantılar ve güvenlik ayarları bölümünde varsayılanları (256 MB bellek) genişletebilir ve değiştirebilir, 1 GB'a güncelleyebilirsiniz.

83d757e6c38e10.png

Next simgesini tıkladıktan sonra Çalışma zamanı, Kaynak kodu ve giriş noktası ayarlarını yapabilirsiniz.

Bu işlev için Inline editor değerini koruyun:

b6646ec646082b32.png

Java çalışma zamanlarından birini seçin (ör. Java 11):

f85b8a6f951f47a7.png

Kaynak kodu, bir Java dosyası ve çeşitli meta veriler ile bağımlılıklar sağlayan bir pom.xml Maven dosyasından oluşur.

Varsayılan kod snippet'ini değiştirmeyin. Bu snippet, yüklenen resmin dosya adını günlüğe kaydeder:

9b7b9801b42f6ca6.png

Şimdilik test amacıyla yürütülecek işlevin adını Example olarak tutun.

İşlevi oluşturup dağıtmak için Deploy simgesini tıklayın. Dağıtım başarılı olduktan sonra işlevler listesinde yeşil daire içinde bir onay işareti görürsünüz:

3732fdf409eefd1a.png

8. İşlevi test etme

Bu adımda, işlevin depolama etkinliklerine yanıt verdiğini test edin.

"Hamburger" (☰) menüsünden Storage sayfasına geri dönün.

Resimler paketini ve ardından Upload files simgesini tıklayarak resim yükleyin.

21767ec3cb8b18de.png

Cloud Console'da tekrar gezinerek Logging > Logs Explorer sayfasına gidin.

İşlevlerinize ayrılmış günlükleri görmek için Log Fields seçicide Cloud Function simgesini seçin. Günlük alanları arasında aşağı kaydırın. İşlevlerle ilgili günlükleri daha ayrıntılı şekilde görmek için belirli bir işlevi de seçebilirsiniz. picture-uploaded işlevini seçin.

İşlevin oluşturulmasından, başlangıç ve bitiş zamanlarından ve gerçek günlük ifadesinden bahseden günlük öğelerini görmeniz gerekir:

e8ba7d39c36df36c.png

Günlük ifademiz Processing file: pic-a-daily-architecture-events.png şeklindedir. Bu, bu resmin oluşturulması ve depolanmasıyla ilgili etkinliğin gerçekten beklendiği gibi tetiklendiği anlamına gelir.

9. Veritabanını hazırlama

Vision API tarafından verilen resimle ilgili bilgileri hızlı, tümüyle yönetilen, sunucusuz ve bulutta yerel bir NoSQL belge veritabanı olan Cloud Firestore veritabanında saklayacaksınız. Cloud Console'un Firestore bölümüne giderek veritabanınızı hazırlayın:

9e4708d2257de058.png

İki seçenek sunulur: Native mode veya Datastore mode. Çevrimdışı destek ve gerçek zamanlı senkronizasyon gibi ek özellikler sunan yerel modu kullanın.

SELECT NATIVE MODE simgesini tıklayın.

9449ace8cc84de43.png

Çok bölgeli bir konum seçin (burada Avrupa'da, ancak ideal olarak işlevinizin ve depolama paketinizin bulunduğu bölgeyle aynı bölge olmalıdır).

CREATE DATABASE düğmesini tıklayın.

Veritabanı oluşturulduktan sonra aşağıdakileri görürsünüz:

56265949a124819e.png

+ START COLLECTION düğmesini tıklayarak yeni bir koleksiyon oluşturun.

pictures adlı koleksiyonu adlandırın.

75806ee24c4e13a7.png

Doküman oluşturmanız gerekmez. Yeni resimler Cloud Storage'da depolanıp Vision API tarafından analiz edildikçe bunları programatik olarak eklersiniz.

Save simgesini tıklayın.

Firestore, yeni oluşturulan koleksiyonda ilk varsayılan dokümanı oluşturur. Bu doküman herhangi bir faydalı bilgi içermediğinden güvenle silebilirsiniz:

5c2f1e17ea47f48f.png

Koleksiyonumuzda programatik olarak oluşturulacak dokümanlar 4 alan içerir:

  • name (dize): Yüklenen resmin dosya adı. Bu ad, belgenin anahtarıdır.
  • labels (dizeler dizisi): Vision API tarafından tanınan öğelerin etiketleri
  • color (dize): Baskın rengin onaltılık renk kodu (ör. #ab12ef)
  • created (tarih): Bu resmin meta verilerinin depolandığı zaman damgası
  • thumbnail (boolean): Bu resim için küçük resim oluşturulmuşsa mevcut ve doğru olacak isteğe bağlı bir alan

Küçük resimleri olan fotoğrafları bulmak için Firestore'da arama yapacağımız ve oluşturulma tarihine göre sıralama yapacağımız için bir arama dizini oluşturmamız gerekir.

Cloud Shell'de aşağıdaki komutla dizini oluşturabilirsiniz:

gcloud firestore indexes composite create \
  --collection-group=pictures \
  --field-config field-path=thumbnail,order=descending \
  --field-config field-path=created,order=descending

Alternatif olarak, sol taraftaki gezinme sütununda Indexes simgesini tıklayıp aşağıda gösterildiği gibi bir bileşik dizin oluşturarak da Cloud Console'dan yapabilirsiniz:

ecb8b95e3c791272.png

Create simgesini tıklayın. Dizin oluşturma işlemi birkaç dakika sürebilir.

10. İşlevi güncelleme

Resimlerimizi analiz etmek için Vision API'yi çağırmak üzere işlevi güncellemek ve meta verileri Firestore'da depolamak için Functions sayfasına geri dönün.

"Hamburger" (☰) menüsünden Cloud Functions bölümüne gidin, işlev adını tıklayın, Source sekmesini seçin ve ardından EDIT düğmesini tıklayın.

Öncelikle, Java işlevimizin bağımlılıklarını listeleyen pom.xml dosyasını düzenleyin. Cloud Vision API Maven bağımlılığını eklemek için kodu güncelleyin:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cloudfunctions</groupId>
  <artifactId>gcs-function</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.target>11</maven.compiler.target>
    <maven.compiler.source>11</maven.compiler.source>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>26.1.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>com.google.cloud.functions</groupId>
      <artifactId>functions-framework-api</artifactId>
      <version>1.0.4</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-firestore</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-vision</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.cloud</groupId>
      <artifactId>google-cloud-storage</artifactId>
    </dependency>
  </dependencies>

  <!-- Required for Java 11 functions in the inline editor -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <excludes>
            <exclude>.google/</exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Bağımlılıklar güncellendiğine göre, Example.java dosyasını özel kodumuzla güncelleyerek işlevimizin kodu üzerinde çalışacaksınız.

Fareyi Example.java dosyasının üzerine getirin ve kalemi tıklayın. Paket adını ve dosya adını src/main/java/fn/ImageAnalysis.java ile değiştirin.

ImageAnalysis.java içindeki kodu aşağıdaki kodla değiştirin. Bu konu sonraki adımda açıklanacaktır.

package fn;

import com.google.cloud.functions.*;
import com.google.cloud.vision.v1.*;
import com.google.cloud.vision.v1.Feature.Type;
import com.google.cloud.firestore.*;
import com.google.api.core.ApiFuture;

import java.io.*;
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
import java.util.logging.Logger;

import fn.ImageAnalysis.GCSEvent;

public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
    private static final Logger logger = Logger.getLogger(ImageAnalysis.class.getName());

    @Override
    public void accept(GCSEvent event, Context context) 
            throws IOException, InterruptedException, ExecutionException {
        String fileName = event.name;
        String bucketName = event.bucket;

        logger.info("New picture uploaded " + fileName);

        try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
            List<AnnotateImageRequest> requests = new ArrayList<>();
            
            ImageSource imageSource = ImageSource.newBuilder()
                .setGcsImageUri("gs://" + bucketName + "/" + fileName)
                .build();

            Image image = Image.newBuilder()
                .setSource(imageSource)
                .build();

            Feature featureLabel = Feature.newBuilder()
                .setType(Type.LABEL_DETECTION)
                .build();
            Feature featureImageProps = Feature.newBuilder()
                .setType(Type.IMAGE_PROPERTIES)
                .build();
            Feature featureSafeSearch = Feature.newBuilder()
                .setType(Type.SAFE_SEARCH_DETECTION)
                .build();
                
            AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
                .addFeatures(featureLabel)
                .addFeatures(featureImageProps)
                .addFeatures(featureSafeSearch)
                .setImage(image)
                .build();
            
            requests.add(request);

            logger.info("Calling the Vision API...");
            BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
            List<AnnotateImageResponse> responses = result.getResponsesList();

            if (responses.size() == 0) {
                logger.info("No response received from Vision API.");
                return;
            }

            AnnotateImageResponse response = responses.get(0);
            if (response.hasError()) {
                logger.info("Error: " + response.getError().getMessage());
                return;
            }

            List<String> labels = response.getLabelAnnotationsList().stream()
                .map(annotation -> annotation.getDescription())
                .collect(Collectors.toList());
            logger.info("Annotations found:");
            for (String label: labels) {
                logger.info("- " + label);
            }

            String mainColor = "#FFFFFF";
            ImageProperties imgProps = response.getImagePropertiesAnnotation();
            if (imgProps.hasDominantColors()) {
                DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
                ColorInfo colorInfo = colorsAnn.getColors(0);

                mainColor = rgbHex(
                    colorInfo.getColor().getRed(), 
                    colorInfo.getColor().getGreen(), 
                    colorInfo.getColor().getBlue());

                logger.info("Color: " + mainColor);
            }

            boolean isSafe = false;
            if (response.hasSafeSearchAnnotation()) {
                SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();

                isSafe = Stream.of(
                    safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
                    safeSearch.getSpoof(), safeSearch.getViolence())
                .allMatch( likelihood -> 
                    likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
                );

                logger.info("Safe? " + isSafe);
            }

            // Saving result to Firestore
            if (isSafe) {
                FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
                Firestore pictureStore = firestoreOptions.getService();

                DocumentReference doc = pictureStore.collection("pictures").document(fileName);

                Map<String, Object> data = new HashMap<>();
                data.put("labels", labels);
                data.put("color", mainColor);
                data.put("created", new Date());

                ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());

                logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
            }
        }
    }

    private static String rgbHex(float red, float green, float blue) {
        return String.format("#%02x%02x%02x", (int)red, (int)green, (int)blue);
    }

    public static class GCSEvent {
        String bucket;
        String name;
    }
}

968749236c3f01da.png

11. İşlevi keşfetme

Şimdi çeşitli ilginç bölümleri daha yakından inceleyelim.

İlk olarak, Maven pom.xml dosyasına belirli bağımlılıkları ekliyoruz. Google Java İstemci Kitaplıkları, bağımlılık çakışmalarını ortadan kaldırmak için Bill-of-Materials(BOM) yayınlar. Bu özelliği kullandığınızda, bağımsız Google istemci kitaplıkları için herhangi bir sürüm belirtmeniz gerekmez.

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>libraries-bom</artifactId>
        <version>26.1.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

Ardından, Vision API için bir istemci hazırlıyoruz:

...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...

Şimdi de işlevimizin yapısını ele alalım. Gelen etkinlikten ilgilendiğimiz alanları yakalayıp tanımladığımız GCSEvent yapısıyla eşleriz:

...
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
    @Override
    public void accept(GCSEvent event, Context context) 
            throws IOException, InterruptedException,     
    ExecutionException {
...

    public static class GCSEvent {
        String bucket;
        String name;
    }

İmzaya dikkat edin. Ayrıca, Cloud Function'ı tetikleyen dosyanın ve paketin adını nasıl aldığımıza da dikkat edin.

Referans olarak, etkinlik yükünün görünümü aşağıda verilmiştir:

{
  "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 istemcisi üzerinden gönderilecek bir istek hazırlıyoruz:

ImageSource imageSource = ImageSource.newBuilder()
    .setGcsImageUri("gs://" + bucketName + "/" + fileName)
    .build();

Image image = Image.newBuilder()
    .setSource(imageSource)
    .build();

Feature featureLabel = Feature.newBuilder()
    .setType(Type.LABEL_DETECTION)
    .build();
Feature featureImageProps = Feature.newBuilder()
    .setType(Type.IMAGE_PROPERTIES)
    .build();
Feature featureSafeSearch = Feature.newBuilder()
    .setType(Type.SAFE_SEARCH_DETECTION)
    .build();
    
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
    .addFeatures(featureLabel)
    .addFeatures(featureImageProps)
    .addFeatures(featureSafeSearch)
    .setImage(image)
    .build();

Vision API'nin 3 temel özelliğini istiyoruz:

  • Etiket algılama: Bu resimlerde ne olduğunu anlamak için
  • Resim özellikleri: Resmin ilgi çekici özelliklerini vermek için (Resmin baskın rengiyle ilgileniyoruz)
  • Güvenli Arama: Resmin gösterilmesinin güvenli olup olmadığını belirlemek için (yetişkinlere uygun, tıbbi, müstehcen veya şiddet barındıran içerik barındırmamalıdır)

Bu noktada, Vision API'yi çağırabiliriz:

...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = 
                            vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...

Referans olarak, Vision API'den gelen yanıtın nasıl göründüğünü aşağıda bulabilirsiniz:

{
  "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
}

Hata döndürülmezse devam edebiliriz. Bu nedenle, aşağıdaki if bloğunu kullanırız:

AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
     logger.info("Error: " + response.getError().getMessage());
     return;
}

Resimde tanınan öğelerin, kategorilerin veya temaların etiketlerini alacağız:

List<String> labels = response.getLabelAnnotationsList().stream()
    .map(annotation -> annotation.getDescription())
    .collect(Collectors.toList());

logger.info("Annotations found:");
for (String label: labels) {
    logger.info("- " + label);
}

Resmin baskın rengini öğrenmek istiyoruz:

String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
    DominantColorsAnnotation colorsAnn = 
                               imgProps.getDominantColors();
    ColorInfo colorInfo = colorsAnn.getColors(0);

    mainColor = rgbHex(
        colorInfo.getColor().getRed(), 
        colorInfo.getColor().getGreen(), 
        colorInfo.getColor().getBlue());

    logger.info("Color: " + mainColor);
}

Ayrıca kırmızı / yeşil / mavi değerlerini, CSS stil sayfalarında kullanabileceğimiz onaltılık renk koduna dönüştürmek için bir hizmet işlevi kullanıyoruz.

Resmin gösterilmesinin güvenli olup olmadığını kontrol edelim:

boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
    SafeSearchAnnotation safeSearch = 
                      response.getSafeSearchAnnotation();

    isSafe = Stream.of(
        safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
        safeSearch.getSpoof(), safeSearch.getViolence())
    .allMatch( likelihood -> 
        likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
    );

    logger.info("Safe? " + isSafe);
}

Yetişkinlere uygun içerik, sahtekarlık, tıbbi içerik, şiddet ve müstehcenlik özelliklerinin olası veya çok olası olup olmadığını kontrol ediyoruz.

Güvenli arama sonucu uygunsa meta verileri Firestore'da saklayabiliriz:

if (isSafe) {
    FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
    Firestore pictureStore = firestoreOptions.getService();

    DocumentReference doc = pictureStore.collection("pictures").document(fileName);

    Map<String, Object> data = new HashMap<>();
    data.put("labels", labels);
    data.put("color", mainColor);
    data.put("created", new Date());

    ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());

    logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}

12. İşlevi dağıtma

İşlevi dağıtma süresi.

604f47aa11fbf8e.png

DEPLOY düğmesine basın. Yeni sürüm dağıtılır ve ilerleme durumunu görebilirsiniz:

13da63f23e4dbbdd.png

13. İşlevi tekrar test edin.

İşlev başarıyla dağıtıldıktan sonra Cloud Storage'a bir resim gönderecek, işlevimizin çağrılıp çağrılmadığını, Vision API'nin ne döndürdüğünü ve meta verilerin Firestore'da depolanıp depolanmadığını göreceksiniz.

Cloud Storage simgesine geri gidin ve laboratuvarın başında oluşturduğumuz paketi tıklayın:

d44c1584122311c7.png

Paket ayrıntıları sayfasına geldiğinizde resim yüklemek için Upload files düğmesini tıklayın.

26bb31d35fb6aa3d.png

"Hamburger" (☰) menüsünden Logging > Logs Gezgini'ne gidin.

İşlevlerinize ayrılmış günlükleri görmek için Log Fields seçicide Cloud Function simgesini seçin. Günlük alanları arasında aşağı kaydırın. İşlevlerle ilgili günlükleri daha ayrıntılı şekilde görmek için belirli bir işlevi de seçebilirsiniz. picture-uploaded işlevini seçin.

b651dca7e25d5b11.png

Günlük listesinde işlevimizin çağrıldığını görebiliyoruz:

d22a7f24954e4f63.png

Günlükler, işlev yürütmenin başlangıcını ve sonunu gösterir. Bu iki satır arasında, console.log() ifadeleriyle işlevimize yerleştirdiğimiz günlükleri görebiliriz. Gördüğümüz:

  • İşlevimizi tetikleyen etkinliğin ayrıntıları,
  • Vision API çağrısından alınan ham sonuçlar,
  • Yüklediğimiz resimde bulunan etiketler,
  • Baskın renkler bilgisi,
  • Resmin gösterilmesinin güvenli olup olmadığı,
  • Sonunda resimle ilgili bu meta veriler Firestore'da depolanır.

9ff7956a215c15da.png

Yine "hamburger" (☰) menüsünden Firestore bölümüne gidin. Data alt bölümünde (varsayılan olarak gösterilir), yeni bir doküman eklenmiş pictures koleksiyonunu görmeniz gerekir. Bu doküman, az önce yüklediğiniz resme karşılık gelir:

a6137ab9687da370.png

14. Temizleme (isteğe bağlı)

Serideki diğer laboratuvarlara devam etmeyi düşünmüyorsanız maliyetleri düşürmek ve genel olarak iyi bir bulut kullanıcısı olmak için kaynakları temizleyebilirsiniz. Kaynakları tek tek aşağıdaki şekilde temizleyebilirsiniz.

Paketi silme:

gsutil rb gs://${BUCKET_PICTURES}

İşlevi silme:

gcloud functions delete picture-uploaded --region europe-west1 -q

Koleksiyondan Koleksiyonu sil'i seçerek Firestore koleksiyonunu silin:

410b551c3264f70a.png

Alternatif olarak, projenin tamamını silebilirsiniz:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

15. Tebrikler!

Tebrikler! Projenin ilk anahtar hizmetini başarıyla uyguladınız.

İşlediğimiz konular

  • Cloud Storage
  • Cloud Functions
  • Cloud Vision API
  • Cloud Firestore

Sonraki Adımlar