Notice
Recent Posts
Recent Comments
Today
Total
03-28 20:03
Archives
관리 메뉴

Jeongchul Kim

딥러닝을 활용한 패션 유사 상품 추천 서비스 (3) Google Login 개발 본문

Android

딥러닝을 활용한 패션 유사 상품 추천 서비스 (3) Google Login 개발

김 정출 2017. 6. 27. 15:31


 

딥러닝을 활용한 패션 유사 상품 추천 서비스 (3) Google Login 개발


딥러닝을 활용한 패션 유사 상품 추천 서비스 (1) 기획

딥러닝을 활용한 패션 유사 상품 추천 서비스 (2) Android app 개발


본 프로젝트는 사용자가 직접 의류를 촬영하거나, 의류의 이미지로 검색하여 비슷한 상품을 추천하고 관련 쇼핑몰로 연계해주는 시스템을 개발하고자 한다. 디지털 이미지의 사용이 기하급수적으로 증가함에 따라, 텍스트에 의한 이미지 검색 방법이 한계에 이르게 되었다. 텍스트 검색은 키워드 기반 검색으로 가격, 브랜드, 색상 등과 같은 정형화된 속성만을 분류하여 찾을 수 있다. 시각적인 요소가 중요한 패션 분야에서는 텍스트 검색을 통해 찾은 결과가 사용자가 검색하려는 의도에 충족시키지 못한다. 텍스트 검색의 한계점을 보완하고자, 각 분야에서는 쉽게 제품 검색을 할 수 있도록 이미지 검색 기능과 유사한 이미지를 추천해주는 시스템을 개발하고 있다.


이를 위해 효과적으로 구축하기 위해서 딥러닝 기반의 컨볼루션 신경망(CNN; Convolutional Neural Network) 기술을 적용해 주어진 이미지 안에서 상품에 해당하는 영역을 자동으로 인식하고, 그 영역에서 검색에 필요한 각각의 요소(색상, 모양, 패턴)를 추출해 분류 과정을 거치고, 클러스터링 기법을 통해 가장 유사한 이미지 군을 추천하는 시스템을 개발한다.


프로젝트 환경

Android Studio 2.2.3, Android 4.4(KitKat) API 19


APP UI

Google Login API Set-UP

https://developers.google.com/identity/sign-in/android/start-integrating


Google Login API를 사용하기 위해 Google Play Services SDK를 설치합니다.

메뉴에서 Tools -> Android -> SDK Manager 클릭합니다.


Default Settings 화면에서 SDK Tools를 선택하고 Google Play Services를 선택하고 OK 버튼을 클릭합니다.


OK 버튼을 클릭합니다.


설치가 진행됩니다.


끝나면  Finish 버튼을 클릭합니다.


Google Login을 먼저해주세요

https://developers.google.com/identity/sign-in/android/start-integrating

사이트에서 Get a configuration file 파란색 버튼을 클릭합니다.


다음의 사이트에서 앱 이름과 패키지 이름을 입력합니다.

예시는 다음과 같습니다.

app : forstyle (앱이름)

pacakge name : kookmin.cs.msdj (패키지명)

country/region : 대한민국

Choose and configure services 버튼을 클릭합니다.


Google Service를 사용하기 위해 Android Signing Certificate SHA-1를 입력해야 합니다.




Google 로그인 및 앱 초대와 같은 일부 Google Play 서비스에서는 서명 인증서의 SHA-1을 제공해야 앱에서 OAuth2 클라이언트 및 API 키를 만들 수 있습니다. SHA-1을 받으려면 다음 지침을 따르십시오.

터미널을 열고 keytoolJava와 함께 제공된 유틸리티 를 실행하여 인증서의 SHA-1 지문을 얻습니다. 릴리스 및 디버그 인증서 지문을 모두 얻어야합니다.


https://developers.google.com/android/guides/client-auth


Android Studio 하단부 메뉴에 Terminal을 클릭하면 Terminal 창이 열립니다. 또는 명령프롬프트 cmd를 이용하셔도 됩니다.


명령 프롬프트에서 실행하겠습니다.

> keytool

입력합니다.


존재하지 않는 명령이라면 java의 bin 이 시스템 환경변수에 설정되어 있지 않는 경우입니다.

제어판에서 시스템에 들어가 고급 시스템 설정 버튼을 클릭합니다.


환경변수 버튼을 클릭합니다.


변수에서 Path를 선택하고 편집 버튼을 클릭합니다.

새로만들기 버튼을 클릭합니다.


Java가 설치된 위치에서 bin를 입력합니다.

C:\Program Files (x86)\Java\jre1.8.0_121\bin


확인 버튼을 누르고 빠져나옵니다.


명령 프롬프트를 새로 켭니다.

> keytool



다음의 명령어를 입력합니다. your-key-name에 비밀번호 입력하고, path-to-production-keystore에 저장할 키 store 위치를 입력합니다.


keystore가 저장되는 위치는 C:\Users\<user>\.android\ 입니다. <user>에 윈도우 유저명을 입력합니다.

다음의 명령어를 입력합니다.


keytool -list -v -keystore C:\Users\<user>\.android\debug.keystore

키 저장소 비밀번호를 입력합니다. android 입니다.


다음과 같이 SHA1 입력이 나옵니다. 이것을 Android Signing Certificate SHA-1 입력합니다.


SHA1 키를 입력하고 ENABLE GOOGLE SIGN-IN 버튼을 클릭합니다.


완료가 되면 Generate configuration file 버튼을 클릭합니다.


다음의 창에서 Download google-services.json 메뉴를 클릭하여 json파일을 다운받습니다.


다운받은 json 파일을 AndroidStudioProjects에 개발 중인 프로젝트의 app 디렉토리에 붙여넣습니다.


Continue Adding-Sign-In 버튼을 클릭합니다.


Goolge Console 사용자인증정보 추가하기

https://console.developers.google.com/apis/


사용자 인증 정보로 이동합니다.


Android Client 를 수정해야 합니다.


패키지명을 패키지명+앱이름까지 기입합니다.


Build Gradle


다음은 Google Services 플러그인을 추가하기 위해 build.gradle을 수정해야 합니다.

build.gradle에는 프로젝트 레벨(project-level)과 앱 레벨(app-level)이 있습니다.

프로젝트 레벨의 build.gradle 부터 수정합니다.


build.gradle(Project : --)

dependencies에 classpath를 추가합니다.


// Top-level build file where you can add configuration options common to all sub-projects/modules.


buildscript {

  repositories {

      jcenter()

  }

  dependencies {

      classpath 'com.android.tools.build:gradle:2.2.3'

      classpath 'com.google.gms:google-services:3.0.0'

      // NOTE: Do not place your application dependencies here; they belong

      // in the individual module build.gradle files

  }

}


allprojects {

  repositories {

      jcenter()

  }

}


task clean(type: Delete) {

  delete rootProject.buildDir

}



build.gradle(Module: app)

상단부에 apply plugin: 에 추가합니다.


apply plugin: 'com.android.application'



android {

  compileSdkVersion 25

  buildToolsVersion "25.0.2"

  defaultConfig {

      applicationId "kookmin.cs.msdj.forstyle"

      minSdkVersion 19

      targetSdkVersion 25

      versionCode 1

      versionName "1.0"

      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

  }

  buildTypes {

      release {

          minifyEnabled false

          proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

      }

  }

}


dependencies {

  compile fileTree(dir: 'libs', include: ['*.jar'])

  androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {

      exclude group: 'com.android.support', module: 'support-annotations'

  })

  compile 'com.android.support:appcompat-v7:25.1.0'

  compile 'com.android.support:design:25.1.0'

  compile 'com.android.support:support-v4:25.1.0'

  compile 'com.google.firebase:firebase-messaging:9.8.0'

  compile 'com.google.android.gms:play-services-auth:9.8.0'

  testCompile 'junit:junit:4.12'

}

apply plugin: 'com.google.gms.google-services'




상단부에 노란색 바로 SyncNow 버튼을 클릭합니다.


도중에 build 창에서 다음의 오류가 나온다면…

execution failed for task 'app processdebuggoogleservices no matching client found


google-services.json 파일을 수정해야 합니다. 패키지명이 동일하지 않을 때 문제가 발생합니다.


json 파일에서 package_name으로 들어가는 두 부분을 앱 이름명까지 합쳐 수정합니다. 즉 build.gradle(module:app)에서 defaultConfig { applicationID:”---”}의 applicationID 와 동일해야합니다.




Set-up 완료되었습니다.


activity_login.xml

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"

  xmlns:app="http://schemas.android.com/apk/res-auto"

  xmlns:tools="http://schemas.android.com/tools"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  android:fitsSystemWindows="true"

  tools:context="kookmin.cs.msdj.forstyle.LoginActivity">


  <android.support.design.widget.AppBarLayout

      android:layout_width="match_parent"

      android:layout_height="wrap_content"

      android:theme="@style/AppTheme.AppBarOverlay">


  </android.support.design.widget.AppBarLayout>


  <include layout="@layout/content_login" />



</android.support.design.widget.CoordinatorLayout>

content_login.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

  xmlns:app="http://schemas.android.com/apk/res-auto"

  xmlns:tools="http://schemas.android.com/tools"

  android:id="@+id/content_login"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  android:paddingBottom="@dimen/activity_vertical_margin"

  android:paddingLeft="@dimen/activity_horizontal_margin"

  android:paddingRight="@dimen/activity_horizontal_margin"

  android:paddingTop="@dimen/activity_vertical_margin"

  android:orientation="vertical"

  app:layout_behavior="@string/appbar_scrolling_view_behavior"

  tools:context="kookmin.cs.msdj.forstyle.LoginActivity"

  tools:showIn="@layout/activity_login">


  <ImageView

      android:layout_marginTop="100dp"

      android:id="@+id/splashscreen"

      android:layout_width="180dp"

      android:layout_height="180dp"

      android:src="@drawable/app_logo_small"

      android:layout_gravity="center"

      />

  <TextView

      android:layout_width="wrap_content"

      android:layout_height="wrap_content"

      android:textSize="30dp"

      android:textColor="@color/colorPrimaryDark"

      android:textStyle="italic"

      android:layout_gravity="center"

      android:text="@string/app_name"/>


  <com.google.android.gms.common.SignInButton

      android:id="@+id/sign_in_button"

      android:layout_width="300dp"

      android:layout_height="wrap_content"

      android:layout_marginTop="100dp"

      android:layout_gravity="center"/>


</LinearLayout>



LoginActivity.java


import android.app.ProgressDialog;

import android.content.Intent;

import android.os.Bundle;

import android.support.annotation.NonNull;


import android.support.v7.app.AppCompatActivity;

import android.support.v7.widget.Toolbar;

import android.util.Log;

import android.view.View;


import com.google.android.gms.auth.api.Auth;

import com.google.android.gms.auth.api.signin.GoogleSignInAccount;

import com.google.android.gms.auth.api.signin.GoogleSignInOptions;

import com.google.android.gms.auth.api.signin.GoogleSignInResult;

import com.google.android.gms.common.ConnectionResult;

import com.google.android.gms.common.SignInButton;

import com.google.android.gms.common.api.GoogleApiClient;

import com.google.android.gms.common.api.OptionalPendingResult;

import com.google.android.gms.common.api.ResultCallback;


public class LoginActivity extends AppCompatActivity implements

      GoogleApiClient.OnConnectionFailedListener, View.OnClickListener{


  private static final String TAG = "LoginActivity";

  private GoogleSignInOptions gso;

  private GoogleApiClient mGoogleApiClient;

  private GoogleSignInResult result;


  private ProgressDialog mProgressDialog;

  private static final int RC_SIGN_IN = 9001;


  @Override

  protected void onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      setContentView(R.layout.activity_login);

      Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

      setSupportActionBar(toolbar);


      // Button listener

      findViewById(R.id.sign_in_button).setOnClickListener(this);


      /* Google Sign-In 은 유저의 아이디와 기본적인 프로필 정보를 요청하도록 개체를 만든다.

      사용자의 전자 메일 주소도 요청하려면 requsetEmail() 를 호출한다. */


      // [START configure_signin]

      // Configure sign-in to request the user's ID, email address, and basic

      // profile. ID and basic profile are included in DEFAULT_SIGN_IN.

      gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).requestEmail().build();

      // [END configure_signin]


      // [START build_client]

      // Build a GoogleApiClient with access to the Google Sign-In API and the

      // options specified by gso.

      mGoogleApiClient = new GoogleApiClient.Builder(this)

              .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)

              .addApi(Auth.GOOGLE_SIGN_IN_API, gso)

              .build();

      // [END build_client]


      // Set the dimensions of the sign-in button.

      SignInButton signInButton = (SignInButton) findViewById(R.id.sign_in_button);

      // [END customize_button]

  }


  // [START onStart]

  @Override

  protected void onStart() {

      super.onStart();

      // 구글의 로그인 결과를 확인한다.

      OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);

      if(opr.isDone()) { // 사용자의 저장된 정보가 유효하다면 Done.

          // If the user's cached credentials are valid, the OptionalPendingResult will be "done"

          // and the GoogleSignInResult will be available instantly.

          result = opr.get();

          handleSignInResult(result); // 로그인을 처리하는 함수로 구글 로그인 결과를 전달한다.

      }else{

          // If the user has not previously signed in on this device or the sign-in has expired,

          // this asynchronous branch will attempt to sign in the user silently.  Cross-device

          // single sign-on will occur in this branch.

          showProgressDialog();

          opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {

              @Override

              public void onResult(@NonNull GoogleSignInResult googleSignInResult) {

                  hideProgressDialog();

                  handleSignInResult(googleSignInResult);

              }

          });

      }

  }

  // [END onStart]


  // [START onActivityResult]

  @Override

  protected void onActivityResult(int requestCode, int resultCode, Intent data) {

      super.onActivityResult(requestCode, resultCode, data);

      // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);

      if (requestCode == RC_SIGN_IN) {

          GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);

          handleSignInResult(result);

      }

  }


  // [START handleSignInResult]

  private void handleSignInResult(GoogleSignInResult result) {

      Log.d(TAG, "handleSignInResult:" + result.isSuccess());

      if (result.isSuccess()) { // 로그인 결과가 성공했따면

          GoogleSignInAccount acct = result.getSignInAccount();

          Log.d(TAG,acct.getDisplayName());

          startActivity(new Intent(LoginActivity.this, MainActivity.class)); // MainActivity를 실행한다.

      }

  }

  // [END handleSignInResult]


  // 구글 로그인을 한 사용자의 정보를 환경변수에 저장한다.

  public void saverPrefsGooglePersonData(GoogleSignInResult result) {

    GoogleSignInAccount acct = result.getSignInAccount(); // Google Login한 사용자의 정보를 가져온다.

     SharedPreferences prefs = getSharedPreferences("GOOGLE_LOGIN",0); // SharedPreference 환경 변수 사용

     SharedPreferences.Editor editor = prefs.edit();

     editor.putString("PERSON_NAME", acct.getDisplayName());

     editor.putString("PERSON_EMAIL", acct.getEmail());

     editor.putString("PERSON_PHOTO_URI", acct.getPhotoUrl().toString());

     Log.d(TAG,"이름 : "+acct.getDisplayName()+" 이메일 주소 : "+acct.getEmail()+" 사용자 이미지 : "+acct.getPhotoUrl().toString());

     editor.commit();

  }



  @Override

  public void onClick(View v) {

      if(v.getId() == R.id.sign_in_button) {

          Intent signInIntent

                  = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);

          startActivityForResult(signInIntent, RC_SIGN_IN);

      }


  }


  @Override

  public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

      Log.d(TAG, "onConnectionFailed : "+connectionResult);

  }


  // 프로그레스바를 설정한다.

  private void showProgressDialog() {

      if (mProgressDialog == null) {

          mProgressDialog = new ProgressDialog(this);

          mProgressDialog.setMessage(getString(R.string.loading));

          mProgressDialog.setIndeterminate(true);

      }

      mProgressDialog.show();

  }


  // 프로그레스바를 숨긴다.

  private void hideProgressDialog() {

      if (mProgressDialog != null && mProgressDialog.isShowing()) {

          mProgressDialog.hide();

      }

  }

}




로그인이 성공적이라면, 메인 액티비티에서 사용자가 로그아웃을 할 수 있도록, 버튼을 구성해야 하며

현재 로그인한 사용자의 이름과 사진을 가져와 보여줍니다.


사용자의 정보를 가져오는 것은 다음의 사이트에서 참고합니다.

https://developers.google.com/identity/sign-in/android/people



GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);

GoogleSignInAccount acct = result.getSignInAccount();

String personName = acct.getDisplayName();

String personGivenName = acct.getGivenName();

String personFamilyName = acct.getFamilyName();

String personEmail = acct.getEmail();

String personId = acct.getId();

Uri personPhoto = acct.getPhotoUrl();



Comments