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

1. Genel Bakış

İlk kod laboratuvarında resimleri bir paket içinde yükleyeceksiniz. Bu işlemle, bir işlev tarafından işlenecek bir dosya oluşturma etkinliği oluşturulur. İşlev, görüntü analizi yapmak ve sonuçları veri deposuna kaydetmek için Vision API'ye çağrı yapar.

d650ca5386ea71ad.png

Neler öğreneceksiniz?

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

2. Kurulum ve Gereksinimler

Kendi hızınızda ortam kurulumu

  1. Google Cloud Console'da oturum açıp 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. İstediğiniz zaman güncelleyebilirsiniz.
  • Proje Kimliği, tüm Google Cloud projelerinde benzersiz olmalıdır ve değiştirilemez (belirlendikten sonra değiştirilemez). Cloud Console, otomatik olarak benzersiz bir dize oluşturur. bunun ne olduğunu umursamıyorsunuz. Çoğu codelab'de proje kimliğini (genellikle PROJECT_ID olarak tanımlanır) referans almanız gerekir. Oluşturulan kimliği beğenmezseniz rastgele bir kimlik daha oluşturabilirsiniz. Alternatif olarak, kendi ölçümünüzü deneyip mevcut olup olmadığına bakabilirsiniz. Bu adımdan sonra değiştirilemez ve proje süresince kalır.
  • Bilginiz için bazı API'lerin kullandığı üçüncü bir değer, yani Proje Numarası daha vardır. Bu değerlerin üçü hakkında daha fazla bilgiyi belgelerde bulabilirsiniz.
  1. Sonraki adımda, Cloud kaynaklarını/API'lerini kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir. Bu codelab'i çalıştırmanın maliyeti, yüksek değildir. Bu eğitim dışında faturalandırma yapılmaması için kaynakları kapatmak isterseniz oluşturduğunuz kaynakları silebilir veya projenin tamamını silebilirsiniz. Yeni Google Cloud kullanıcıları, 300 ABD doları değerindeki ücretsiz denemeden yararlanabilir.

Cloud Shell'i başlatma

Google Cloud dizüstü bilgisayarınızdan uzaktan çalıştırılabilse de 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 bulunan Cloud Shell simgesini tıklayın:

55efc1aaa7a4d3ad.png

Ortamı sağlamak ve bağlamak yalnızca birkaç dakika sürer. Tamamlandığında şuna benzer bir sonuç görmeniz gerekir:

7ffe5cbb04455448

İhtiyacınız olan tüm geliştirme araçlarını bu sanal makinede bulabilirsiniz. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud üzerinde çalışarak ağ performansını ve kimlik doğrulamasını büyük ölçüde iyileştirir. Bu codelab'deki tüm çalışmalarınız tarayıcıda yapılabilir. Herhangi bir şey yüklemeniz gerekmez.

3. API'leri etkinleştir

Bu laboratuvarda Cloud Functions ve Vision API'yi kullanacaksınız ancak bunların önce 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 ifadesini 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'i de etkinleştirebilirsiniz.

Cloud Shell'in içinde aşağıdaki komutu çalıştırın:

gcloud services enable vision.googleapis.com

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

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 konsolundan ( console.cloud.google.com) veya Cloud Shell'den ya da yerel geliştirme ortamınızdan gsutil komut satırı aracıyla yapabilirsiniz.

"Hamburger"den (Gönder) 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çin

197817f20be07678.png

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

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

Herkesin erişebileceği görüntülerle çalışacağınız için bu pakette depolanan tüm resimlerimizin aynı tek tip erişim denetimine sahip olmasını istersiniz.

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

CONTINUE simgesini tıklayın.

Korumayı/Şifrelemeyi Ayarlama

d931c24c3e705a68.png

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

Paket oluşturma işlemini tamamlamak için CREATE düğmesini tıklayın.

allUsers'ı depolama alanı görüntüleyici olarak ekle

Permissions sekmesine gidin:

d0ecfdcff730ea51.png

Aşağıdaki gibi, Storage > Storage Object Viewer rolüne sahip bir allUsers üyesini pakete ekleyin:

e9f25ec1ea0b6cc6.png

SAVE simgesini tıklayın.

5. Paket (gsutil) oluşturma

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 zaten ayarlanmış. Bunu paket adına ekleyebilirsiniz.

Örneğin:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

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

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

Tek tip paket düzeyinde erişim sağlayın:

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 paketiniz olur:

a98ed4ba17873e40.png

Pakete resim yükleyemediğinizi ve yüklenen resimlerin önceki adımda açıklandığı gibi herkese açık olup olmadığını test edin.

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

Depolama tarayıcısına geri döndüğünüzde, paketinizi listede "Herkese açık" olarak görürsünüz. (ilgili grubun içeriğine herhangi birinin erişimi olduğunu hatırlatan bir uyarı işareti dahil).

89e7a4d2c80a0319.png

Paketiniz artık resim almaya hazır.

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

131387f12d3eb2d3.png

Buradan, pakete resim ekleyip ekleyebileceğinizi test etmek için Upload files düğmesini deneyebilirsiniz. Dosya seçici pop-up'ında bir dosya seçmeniz istenir. Seçildikten sonra paketinize yüklenir ve bu yeni dosyayla otomatik olarak ilişkilendirilen 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öreceksiniz. Resmi tıkladığınızda, tarayıcınız ilgili resmin herkese açık URL'sine gider. Bu URL şu biçimde olur:

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

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

Resim adının yanındaki onay kutusunu işaretleyerek DELETE düğmesi etkinleşir 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ştirilecektir.

9d29e8c026a7a53f.png

Create function seçeneğini tıklayın.

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

4bb222633e6f278.png

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

  • Bir URL (ör. web API'si) aracılığıyla çağrılabilen HTTP işlevleri
  • Bazı etkinlikler tarafından tetiklenebilecek arka plan işlevleri.

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

d9a12fcf58f4813c.png

Pakette bir dosya oluşturulduğunda veya güncellendiğinde tetiklenen etkinlik olan Finalize/Create etkinlik türüyle ilgileniyorsunuz:

b30c8859b07dc4cb.png

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

cb15a1f4c7a1ca5f.png

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

c1933777fac32c6a.png

İleri'yi tıklamadan önce Çalışma zamanı, derleme, bağlantılar ve güvenlik ayarları bölümündeki varsayılan değerleri (256 MB bellek) genişletip değiştirebilir ve boyutu 1 GB olarak güncelleyebilirsiniz.

83d757e6c38e10.png

Next düğmesini 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 (ör. Java 11) seçin:

f85b8a6f951f47a7.png

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

Varsayılan kod snippet'ini değiştirmeyin. Yüklenen resmin dosya adı kaydedilir:

9b7b9801b42f6ca6.png

Şimdilik, test amacıyla Example işlevine yürütülecek fonksiyonun adını kullanın.

İşlevi oluşturmak ve dağıtmak için Deploy simgesini tıklayın. Dağıtım başarılı olduktan sonra, işlev listesinde yeşil daire içine alınmış bir onay işareti göreceksiniz:

3732fdf409eefd1a.png

8. İşlevi test etme

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

"Hamburger"den (Gönder) menüsünü açın, Storage sayfasına geri dönün.

Resim yüklemek için resim paketini, ardından Upload files öğesini tıklayın.

21767ec3cb8b18de.png

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

İşlevlerinize özel günlükleri görmek için Log Fields seçicide Cloud Function öğesini seçin. Günlük Alanları'nı kaydırarak ilgili günlüklerin daha ayrıntılı bir görünümünü elde etmek için belirli bir işlev bile seçebilirsiniz. picture-uploaded işlevini seçin.

İşlevin oluşturulmasından bahseden günlük öğeleri, işlevin başlangıç ve bitiş zamanları ile gerçek günlük ifademizi göreceksiniz:

e8ba7d39c36df36c.png

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

9. Veritabanını hazırlama

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

9e4708d2257de058.png

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

SELECT NATIVE MODE seçeneğini tıklayın.

9449ace8cc84de43.png

Çoklu bölge seçin (Avrupa'da ancak tercihen en azından işlevinizin ve depolama paketinizin bulunduğu bölgeyle aynı bölge).

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

Veritabanı oluşturulduktan sonra şunu göreceksiniz:

56265949a124819e.png

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

pictures ad koleksiyonu.

75806ee24c4e13a7.png

Doküman oluşturmanız gerekmez. Yeni resimler Cloud Storage'da depolanıp Vision API tarafından analiz edilirken bu resimleri programatik olarak eklersiniz.

Save simgesini tıklayın.

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

5c2f1e17ea47f48f.png

Koleksiyonumuzda programlı olarak oluşturulacak dokümanlar 4 alan içerecektir:

  • name (dize): yüklenen resmin dosya adı ve aynı zamanda dokümanın anahtarıdır
  • labels (dize dizisi): Vision API tarafından tanınan öğelerin etiketleri
  • color (dize): baskın rengin onaltılık renk kodu (ör. #ab12ef)
  • create (tarih): Bu görsele ait meta verilerin saklandığı zaman damgası
  • thumbnail (boole): Bu resim için bir küçük resim oluşturulmuşsa mevcut ve doğru olacak isteğe bağlı bir alan

Küçük resimleri olan resimleri bulmak için Firestore'da arama yapacağımızdan ve oluşturma tarihine göre sıralayacağımız için bir arama dizini oluşturmamız gerekecek.

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

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

Bu işlemi Cloud Console'dan da yapabilirsiniz. Bunun için soldaki gezinme sütununda Indexes simgesini tıklayın ve ardından aşağıda gösterildiği gibi bir birleşik dizin oluşturun:

ecb8b95e3c791272.png

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

10. İşlevi güncelleme

Functions sayfasına geri dönün.

"Hamburger"den (Gönder) menüsünü açın, Cloud Functions bölümüne gidin, işlev adını tıklayın, Source sekmesini seçin, 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üncel olduğuna 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 getirip kalem simgesini tıklayın. Paket adını ve dosya adını src/main/java/fn/ImageAnalysis.java olarak değiştirin.

ImageAnalysis.java içindeki kodu aşağıdaki kodla değiştirin. Bir 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şfedin

Çeşitli ilginç bölümlere daha yakından bakalım.

Öncelikle belirli bağımlılıkları Maven pom.xml dosyasına ekledik. Google Java İstemci Kitaplıkları bağımlılık çakışmalarını ortadan kaldırmak için bir Bill-of-Materials(BOM) yayınlar. Bu özelliği kullandığınızda, her bir Google istemci kitaplığı 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 bir müşteriyi Vision API için hazırlıyoruz:

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

Şimdi işlevimizin yapısı geliyor. 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;
    }

İmzanın yanı sıra Cloud Functions işlevini tetikleyen dosyanın ve paketin adını nasıl aldığımıza da dikkat edin.

Referans olarak etkinlik yükünün nasıl göründüğüne bakalım:

{
  "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öndermek üzere bir istek hazırlarız:

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: Resimlerdekileri 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ı öğrenmek (yetişkinlere uygun / tıbbi / müstehcen / şiddet barındıran içerik barındırmamalıdır)

Bu noktada Vision API'ye çağrı yapabiliriz:

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

Referans olarak, Vision API'nin yanıtı şu şekilde görünür:

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

Herhangi bir hata döndürülmezse işleme devam edebiliriz. Dolayısıyla, engelleme durumunda neden şu hatayı gördüğümüz:

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

Resimde algılanan öğ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 bir onaltılık renk koduna dönüştürmek için bir yardımcı program işlevinden yararlanı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);
}

Olası veya çok olası olup olmadıklarını görmek için yetişkinlere uygun / adres sahteciliği / tıbbi / şiddet / müstehcenlik özelliklerini kontrol ediyoruz.

Güvenli aramanın sonucu uygunsa meta verileri Firestore'da depolayabiliriz:

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 zamanı.

604f47aa11fbf8e.png

DEPLOY düğmesine bastığınızda yeni sürüm dağıtılır. İlerleme durumunu görebilirsiniz:

13da63f23e4dbbdd.png

13. İşlevi tekrar test etme

İşlev başarıyla dağıtıldıktan sonra Cloud Storage'da bir resim yayınlayarak 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örürsünüz.

Cloud Storage hizmetine geri dönün 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"den (Gönder) menüsünden Logging > Logs Gezgini'ne gidin.

İşlevlerinize özel günlükleri görmek için Log Fields seçicide Cloud Function öğesini seçin. Günlük Alanları'nı kaydırarak ilgili günlüklerin daha ayrıntılı bir görünümünü elde etmek için belirli bir işlev bile seçebilirsiniz. picture-uploaded işlevini seçin.

b651dca7e25d5b11.png

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

d22a7f24954e4f63.png

Günlükler, işlevin yürütülmesinin başlangıcını ve bitişini gösterir. Bunların arasında, console.log() ifadeleriyle işlevimize yerleştirdiğimiz günlükleri görebiliriz. Şunları görürüz:

  • İşlevimizi tetikleyen olayın ayrıntıları,
  • Vision API çağrısından gelen ham sonuçlar,
  • Yüklediğimiz resimde bulunan etiketler,
  • Baskın renklerle ilgili bilgiler,
  • Resmin gösterilmesinin güvenli olup olmadığı,
  • Sonunda resimle ilgili bu meta veriler Firestore'da saklanır.

9ff7956a215c15da.png

Yine "hamburger"den (Gönder) menüsünden Firestore bölümüne gidin. Data alt bölümünde (varsayılan olarak gösterilmektedir), az önce yüklediğiniz resimle ilgili yeni bir doküman eklenmiş pictures koleksiyonunu görürsünüz:

a6137ab9687da370.png

14. Temizleme (İsteğe bağlı)

Serideki diğer laboratuvarlarla devam etmeyi düşünmüyorsanız maliyet tasarrufu yapmak ve genel olarak iyi bir bulut vatandaşı olmak için kaynakları temizleyebilirsiniz. Kaynakları aşağıda açıklandığı şekilde tek tek temizleyebilirsiniz.

Paketi silin:

gsutil rb gs://${BUCKET_PICTURES}

İşlevi silin:

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.

İşlediklerimiz

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

Sonraki Adımlar