Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Face recognition with S3 Vectors

s3_vectors_facce_recognition.livemd

Face recognition with S3 Vectors

Mix.install([
  {:kino, "~> 0.15"},
  {:evision, "~> 0.2.13"},
  {:aws, "~> 1.0"},
  {:hackney, "~> 1.24"}
])

画像の準備

image_files = Path.wildcard("/home/livebook/evision/test-images/*.{jpg,png}")

images =
  image_files
  |> Enum.map(fn image_file ->
    Evision.imread(image_file)
  end)

Kino.Layout.grid(images, columns: 4)

顔特徴量の取得

recognizer =
  Evision.Zoo.FaceRecognition.SFace.init(:default_model,
    backend: Evision.Constant.cv_DNN_BACKEND_OPENCV(),
    target: Evision.Constant.cv_DNN_TARGET_CPU(),
    distance_type: :cosine_similarity,
    cosine_threshold: 0.363,
    l2_norm_threshold: 1.128
  )

detector =
  Evision.Zoo.FaceDetection.YuNet.init(:default_model,
    backend: Evision.Constant.cv_DNN_BACKEND_OPENCV(),
    target: Evision.Constant.cv_DNN_TARGET_CPU(),
    nms_threshold: 0.3,
    conf_threshold: 0.8,
    top_k: 5
  )

[feature_list, visualized_list] =
  images
  |> Enum.reduce([[], []], fn image, [feature_acc, visualized_acc] ->
    results = Evision.Zoo.FaceDetection.YuNet.infer(detector, image)

    bbox = Evision.Mat.to_nx(results, Nx.BinaryBackend)[0][0..-2//1]

    feature =
      recognizer
      |> Evision.Zoo.FaceRecognition.SFace.infer(image, bbox)
      |> Evision.Mat.to_nx()
      |> Evision.Mat.from_nx()

    visualized = Evision.Zoo.FaceDetection.YuNet.visualize(image, results[0])

    [[feature | feature_acc], [visualized | visualized_acc]]
  end)
  |> Enum.map(&Enum.reverse/1)

Kino.Layout.grid(visualized_list, columns: 4)
feature_list
|> hd()
|> Evision.Mat.to_nx()
Evision.Zoo.FaceRecognition.SFace.match_feature(
  recognizer,
  Enum.at(feature_list, 8),
  Enum.at(feature_list, 15)
)
Evision.Zoo.FaceRecognition.SFace.match_feature(
  recognizer,
  Enum.at(feature_list, 8),
  Enum.at(feature_list, 0)
)

S3 Vectors

アクセス用関数の準備

access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")
region_input = Kino.Input.text("REGION")

[
  access_key_id_input,
  secret_access_key_input,
  region_input
]
|> Kino.Layout.grid(columns: 3)
client =
  AWS.Client.create(
    Kino.Input.read(access_key_id_input),
    Kino.Input.read(secret_access_key_input),
    Kino.Input.read(region_input)
  )
request_to_s3_vectors = fn (client, path, payload) ->
  now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
  encoded_payload = AWS.Client.encode!(client, payload, :json)

  headers = [
    {"Host", "s3vectors.us-east-1.api.aws"},
    {"Content-Type", "application/json"}
  ]

  url = "https://s3vectors.us-east-1.api.aws#{path}"

  siged_headers =
    AWS.Signature.sign_v4(
      %{client | service: "s3vectors"}, 
      now,
      :post,
      url,
      headers,
      encoded_payload
    )

  {:ok, %{body: body}} =
    AWS.Client.request(client, :post, url, encoded_payload, siged_headers, [])

  JSON.decode!(body)
end

ベクトルバケットの作成

request_to_s3_vectors.(client, "/ListVectorBuckets", %{"maxResults" => 20})
request_to_s3_vectors.(client, "/CreateVectorBucket", %{
  "vectorBucketName" => "face-vectors",
})
vectorBucket =
  client
  |> request_to_s3_vectors.("/GetVectorBucket", %{"vectorBucketName" => "face-vectors"})
  |> Map.get("vectorBucket")

インデックスの作成

request_to_s3_vectors.(client, "/CreateIndex", %{
  "vectorBucketName" => "face-vectors",
  "indexName" => "face-vectors-index",
  "dataType" => "float32",
  "dimension" => 128,
  "distanceMetric" => "cosine"
})
vectorIndex =
  client
  |> request_to_s3_vectors.("/GetIndex", %{
    "vectorBucketName" => "face-vectors",
    "indexName" => "face-vectors-index"
  })
  |> Map.get("index")

データの登録

vaectors =
  feature_list
  |> Enum.zip(image_files)
  |> Enum.map(fn {feature, image_file} ->
    %{
      "key" => Path.basename(image_file),
      "data" => %{
        "float32" => feature |> Evision.Mat.to_nx() |> Nx.to_flat_list(),
      },
      "metadata" => %{
        "name" => image_file |> Path.basename() |> String.split("-") |> hd()
      }
    }
  end)
request_to_s3_vectors.(client, "/PutVectors", %{
  "indexArn" => Map.get(vectorIndex, "indexArn"),
  "vectors" => vaectors
})
request_to_s3_vectors.(client, "/ListVectors", %{
  "indexArn" => Map.get(vectorIndex, "indexArn"),
  "maxResults" => 20,
  "returnData" => true,
  "returnMetadata" => true
})

データの検索

query_vector =
  feature_list
  |> Enum.at(8)
  |> Evision.Mat.to_nx()
  |> Nx.to_flat_list()
request_to_s3_vectors.(client, "/QueryVectors", %{
  "indexArn" => Map.get(vectorIndex, "indexArn"),
  "queryVector" => %{
    "float32" => query_vector
  },
  "returnDistance" => true,
  "returnMetadata" => true,
  "topK" => 5
})

データの削除

request_to_s3_vectors.(client, "/DeleteVectors", %{
  "indexArn" => Map.get(vectorIndex, "indexArn"),
  "keys" => image_files |> Enum.map(&Path.basename(&1))
})
request_to_s3_vectors.(client, "/ListVectors", %{
  "indexArn" => Map.get(vectorIndex, "indexArn"),
  "maxResults" => 20
})

インデックスの削除

request_to_s3_vectors.(client, "/DeleteIndex", %{
  "indexArn" => Map.get(vectorIndex, "indexArn")
})
request_to_s3_vectors.(client, "/ListIndexes", %{
  "maxResults" => 20,
  "vectorBucketArn" => Map.get(vectorBucket, "vectorBucketArn")
})

ベクトルバケットの削除

request_to_s3_vectors.(client, "/DeleteVectorBucket", %{
  "vectorBucketArn" => Map.get(vectorBucket, "vectorBucketArn")
})
request_to_s3_vectors.(client, "/ListVectorBuckets", %{"maxResults" => 20})