구글 OAuth 기능 추가 및 이미지 처리 연결
1. 구글 OAuth추가
백엔드만으로 OAuth 기능을 dj_rest_auth와 django_allauth를 이용하여 토큰을 발급하는 view를 만들어 Oauth를 적용하기로 하였다.
# views.py
from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from allauth.socialaccount.providers.google import views as google_view
class GoogleLogin(SocialLoginView):
adapter_class = google_view.GoogleOAuth2Adapter
callback_url = GOOGLE_CALLBACK_URI
client_class = OAuth2Client
위 view로 code와 access_token을 body로 실어 요청을 보내자, access token과 refresh token이 아닌 key가 반환되었다.
이번 프로젝트에서는 이를 직접 토큰을 발행하는 코드를 작성하여 해결하였다.
class GoogleTokenView(APIView):
def post(self, request):
access_token = request.data.get("access_token")
if access_token is None:
return Response(
{"message": "토큰이 없습니다."}, status=status.HTTP_401_UNAUTHORIZED
)
headers = {"Authorization": f"Bearer {access_token}"}
user_info_request = requests.get(
"https://www.googleapis.com/oauth2/v2/userinfo", headers=headers
)
user_data = user_info_request.json()
email = user_data.get("email")
if email is None:
return Response(
{"message": "유저정보에 접근할 수 없습니다."}, status=status.HTTP_401_UNAUTHORIZED
)
username = email.split("@")[0] + "_g"
password = "0000"
try:
user = User.objects.get(email=email)
except:
# 회원 등록 후 다시 조회
if user.logintype != "GOOGLE":
return Response(
{"message": "소셜 로그인 이메일이 아닙니다."},
status=status.HTTP_400_BAD_REQUEST,
)
refresh = RefreshToken.for_user(user)
refresh["email"] = user.email
refresh["username"] = user.username
refresh["logintype"] = user.logintype
return Response(
{
"refresh": str(refresh),
"access": str(refresh.access_token),
},
status=status.HTTP_200_OK,
)
이 문제는 settings.py가 잘 설정되지 않아 기본설정대로 동작한 것이 원인이다. 공식문서를 참조하자 다음과 같이 설정하면 해결된다고 한다.
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
)
...
}
REST_AUTH = {
...
'USE_JWT': True, # enable JWT authentication in dj-rest-auth
'JWT_AUTH_COOKIE': 'my-app-auth', # Declare what you want the cookie key to be called
'JWT_AUTH_REFRESH_COOKIE': 'my-refresh-token',
}
2. 이미지 처리 모델
먼저 Dlib을 통해 안면을 locate한다.
detector = dlib.get_frontal_face_detector()
img = cv2.imread(input_pic_url)
dets = detector(img)
안면이 여러개 검출될 경우 랜덤하게 한개를 선택한다.
if len(dets) >= 1:
# 얼굴 선택 랜덤
face_index = random.randrange(len(dets))
det = dets[face_index]
x1 = det.left()
y1 = det.top()
x2 = det.right()
y2 = det.bottom()
h, w, c = img.shape
이후 안면의 위/아래/왼쪽/오른쪽 의 여백 공간의 크기를 저장한다.
h, w, c = img.shape
center_x = (x2 + x1) // 2
center_y = (y2 + y1) // 2
a = center_y
b = center_x
c = w - b
d = h - a
이후 가장 여백이 큰 곳 부터 스티커이미지의 부착을 시도한다. 이때 오류가 발생할 시 다른 영역에 시도해본다.
target_list = [a, b, c, d]
target = target_list.pop(target_list.index(max(target_list)))
# target = search_target
while True:
pt1, pt2, sticker_img, target = select_target(
target, a, b, c, d, x1, x2, y1, y2, center_x, center_y
)
# 스티커 이미지 크기 변경
sticker_width = int(abs(pt2[0] - pt1[0]))
sticker_height = int(abs(pt2[1] - pt1[1]))
sticker_resized = cv2.resize(
sticker_img, dsize=(sticker_width, sticker_height)
)
# 알파 채널 값(투명도) 계산
alpha = sticker_resized[:, :, 3] / 255.0
alpha = np.expand_dims(alpha, axis=2)
overlay_rgb = sticker_resized[:, :, :3]
try:
# 스티커 부착 시도 : 스티커 이미지가 원본 이미지를 넘어갈 시 오류 발생
except:
# 실패 시 타겟을 변경해 시도
target = target_list.pop(target_list.index(max(target_list)))
continue
break
cv2.imwrite(save_uri, img)
이때, 수정할 이미지파일의 열고 닫음은 cv2.imread, cv2.imwrite 메소드를 이용한다. cv2는 이미지 파일을 numpy.ndarray 형식으로 불러온다.
그러나 Django에서는 이미지 파일을 models.ImageField에 FieldFile에 InMemoryUploadFile로 불러온다.
이러한 형식의 차이를 어떻게 극복하여 두 부분을 연결할지 고민하였다.
시도 1. 열린 파일을 직접 타입 변환하기
# img가 InMemoryUploadFile입니다
file_bytes = numpy.asarray(bytearray(img.file.read()), dtype=numpy.uint8)
print(type(file_bytes))
img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
print(type(img)
numpy의 asarray와 cv2의 imdecode를 이용해 django의 이미지 필드에 저장된 파일을 cv2에서 다룰 수 있게 타입을 변환할 수 있다.
그러나 역으로 변환하는 방법은 따로 찾지 못했다.
해결: 파일 저장후 경로로 주고 받기
1. ImageField는 저장된 파일 경로를 저장한다.
2. cv2는 저장된 파일을 열 수 있고, 파일을 저장할 수 있다.
위 두가지 사실을 이용하여, 이미지 처리 모듈과 DRF 모듈이 서로 파일 경로를 주고 받아 열고 닫게 하는 것으로 해결할 수 있었다.
# views.py
def post(self, request, *args, **kwargs):
"""PicgenView.post
post요청 시 입력받은 사진으로 변환된 사진을 생성하여 반환합니다.
"""
Picture.objects.filter(article=None, author=request.user).delete()
serializer = PictureSerializer(data=request.data) # 경로를 인자로 넣어줌
if serializer.is_valid():
orm = serializer.save(author=request.user)
change_pic = picture_generator("media/" + orm.__dict__["input_pic"])
orm.change_pic = change_pic.replace("media/", "")
orm.save()
new_serializer = PictureSerializer(instance=orm)
return Response(new_serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# image_process.py
def picture_generator(input_pic_url):
global h, w
detector = dlib.get_frontal_face_detector()
img = cv2.imread(input_pic_url)