본문 바로가기

프로젝트/엘리스 AI 트랙

팀 프로젝트 - 이게모약

엘리스 AI트랙의 마지막 프로젝트는 인공지능 프로젝트였다.

자연어 처리 vs 이미지 처리

 

무엇을 선택하겠습니까, 휴먼

인공지능을 이용하여 이미지를 분석하고 인식하는게 흥미로웠다.

이번 마지막 프로젝트는 엘리스에서 임의로 팀배정을 해주었고,

저번 프로젝트때 같이했던 팀원 한분과 다시 만나게 되어서 기분이 좋았다 ㅎㅎ

 

팀원들과 분위기가 좋아서 아이디어가 샘솟았지만

우리팀에 의료종사자분이 두명이나 있어서 관련된 도메인 지식이 있어서,

알약 인공지능 인식 프로젝트로 주제를 정하게 되었다.

 

병원에서 일하면서 환자분들의 복용약을 일일히 조사해야했던 불편한 점이 가장 컸고,

복용하는 약의 가짓 수가 많은 만성질환 환자 및 보호자들을 위한 서비스를 만들기로 했다.

 

이번 프로젝트에서 나의 목표는 모바일 화면에서 볼 수 있도록 만드는 것이다.

 

[01] React-webcam 라이브러리


내가 프로젝트에서 맡았던 첫번째는 사진을 찍거나 이미지를 업로드하여

인공지능 모델이 분석할 수 있도록 만드는 기능이었다. 처음에는 webcam라이브러리를 이용하려고했으나

PC상에서는 잘 작동하지만, 모바일에서는 호환이 되지 않는 이슈때문에 포기하게 되었다.

 

//React-Webcam 라이브러리 사용

import React, { useRef, useState, useCallback } from 'react';
import Webcam from 'react-webcam';

const CameraPage = () => {
  const webcamRef = useRef(null);
  const [imgSrc, setImgSrc] = useState(null);

  const capture = useCallback(() => {
    const imageSrc = webcamRef.current.getScreenshot();
    setImgSrc(imageSrc);
  }, [webcamRef, setImgSrc]);

  return (
    <>
      <Webcam audio={false} ref={webcamRef} screenshotFormat="image/jpeg" mirrored="true" />
      <button type="button" onClick={capture}>
        사진 촬영
      </button>
      {imgSrc && <img src={imgSrc} alt="사진" />}
    </>
  );
};

export default CameraPage;

 

[02] 이미지 업로드 & 미리보기


다른 방법을 찾다보니 input태그의 속성값에  type="file" accept="image/*"를 넣으면

안드로이드, IOS에서도 사진을 첨부하거나 촬영해서 올릴 수 있다는 것을 찾게 되었다.

또한 FileReader와 onLoad를 이용하여 이미지를 미리보기 하는 방식으로 코드를 넣었다.

 

const WebcamBox = () => {
  const navigate = useNavigate();
  const [file, setFile] = useState(null);
  const [imgSrc, setImgSrc] = useState(null);

  // 서버에 파일 전송하기
  const submitFile = () => {
    const uploadFile = async (e) => {
      e.preventDefault();
      const uploadImg = e.target[0].files[0];

      const formData = new FormData();
      formData.append('files', uploadImg);

      const URL = 'localhost:8000/api/result-photo/';

      try {
        const response = axios.post(URL, formData, {
          header: {
            'Content-Type': 'multipart/form-data',
          },
        });
        console.log(response);
        navigate('/scanning'); // useHistory 상위호환
      } catch (err) {
        console.log(err);
      }
    };
    uploadFile();
  };

  // 미리보기 이미지 띄우기
  const previewImg = () => {
    if(!file) return false;

    const reader = new FileReader();
    reader.onload = () => (
      setImgSrc(`${reader.result}`)
    );
    reader.readAsDataURL(file[0]);
  };

  return (
    <>
      <div>
        {!imgSrc ? (
          <input id="captureFile" type="file" capture="camera" accept="image/*" onChange={(e) => setFile(e.target.files)} />
        ) : (
          <PreviewImgStyle src={imgSrc} alt="알약사진" />
        )}
      </div>
      <button type="button" onClick={previewImg}>
          선택 완료
      </button>
    </>
  );
};

export default WebcamBox;

 

[03] 파일 첨부 대신 이미지 넣기


input 태그에 file속성을 넣으면 파일첨부라는 못생긴 버튼이 하나 생긴다.

보기좋게 꾸미기 위해서 여러 방법을 찾아보다가 label값을 주고

input 태그는 display: none을 해서 보이지 않게 해둔 다음,

label태그를 꾸미는게 간편하다는걸 알게 되었다.

 

사진을 찍은 뒤 API 요청을 보내 인공지능 모델을 불러와서 결과값을 반환받는데 성공했다!

 

<l htmlFor="input-file">
    <img src='images/이미지샘플.png' />
</label>
<input
    id="input-file"
    type="file"
    capture="camera"
    accept="image/*"
    style={{display:"none"}} //안보이게 가려두기
    onChange={(e) => setFile(e.target.files)}
/>

 

[04] useNavigate로 state값 넘겨주기


알약사진을 검색하고 난 뒤에 해당 알약의 일련번호를 다음 페이지로 넘길 때 같이 전달해줘야하는 기능이 필요했다.

전에 react-router-dom에서는 페이지를 이동할 때 useHistory를 사용하였는데, 최근에 새로 변경이 되었다.

useHistory대신에 useNavigate를 사용하는 것인데, 여기에 특정 state값을 같이 넘겨서 보내줄 수가 있다는게 신기했다.

state를 받을 때는 useLocation을 사용해서 받아올 수가 있었다.

 

// 보내는 페이지

import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();

const submitImg = async () => {
    const formData = new FormData();
    formData.append('files', file[0]);

    const URL = 'http://127.0.0.1:8000/api/result-photo/';

    try {
      console.log('file', file[0]);
      const response = await axios.post(URL, formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      });
      console.log(response);
	 // useHistory에서 useNavigate로 바뀜
      navigate('/scan-success', {
        state: {
          pillData: response.data,
        },
      });
    } catch (err) {
      console.log(err);
    }
  };

 

// 받는 페이지


import { useLocation } from 'react-router';

const location = useLocation();
console.log(location.state.pillData);

 

[05] 이미지 다운로드 받기


프로젝트의 기능중에 알약상자를 한눈에 볼 수 있도록 다운로드 받아 사진으로 저장하는 기능을 만들어야 했다.

 html2canvas를 이용하고, useRef로 해당 컴포넌트의 좌표를 찍어주면 해당 태그를 이미지로 변환해주게 된다.

실제로 내가 만든 페이지를 이미지로 저장할 수 있게 된다는게 재밌었다 ㅎㅎ

 

import html2canvas from 'html2canvas';
import { saveAs } from 'file-saver';

const shareImgHandler = async () => {
    window.scrollTo(0, 0); // 상단으로 위치를 맞춰줘야 이미지가 한쪽이 짤리지 않는다.
    await html2canvas(pillBoxRef.current).then(async (canvas) => {
      saveAs(canvas.toDataURL(), 'pillBox.png');
    });

 

[06] 이게모약 로고 만들기


이게모약의 메인페이지이자 로고를 만들기 위해 유용한 사이트를 알게 되었다.

https://logomakr.com/app/6KdiDk

여기서 로고를 손쉽게 제작할 수 있다.

 

이게모약 메인페이지

 

ㅇㄱㅁㅇ 초성으로 만든 로고

 

[07] 프론트/백엔드 나눠서 작업하기


프로젝트를 진행하면서 git 브랜치를 front와 back으로 나눠서 진행했는데, 

front에서 작업중에 git pull origin back을 할 경우 front에서 작업했던 것들이 바뀌어버리는 상황이 생겼었다ㅠ

back브랜치에서는 front작업이 반영이 안되어있어서 두 브랜치를 한번에 관리하기가 어려웠다.

나중에 알고보니 나말고 다른 팀원들은 디렉토리를 서로 나눠서 작업하고 있었다.

프론트와 백엔드 디렉토리를 각각 만들고 서로 다른 디렉토리에서 작업하는게 생각보다 편리했다.

 

1) 폴더를 2개 만들은 다음 프론트 / 백엔드 폴더라 나눈다.
2) git clone [gitlab HTTP 주소] -> git checkout front (또는 back)
3) 백엔드 : venv 설치한 이후 pip install -r requirements.txt로 라이브러리 업데이트
4) 프론트 : venv 설치한 이후 yarn upgrade로 라이브러리 업데이트
5) git commit message 등록해두기

 

- git branch 생성하고 삭제하기 -

//브랜치 생성하기(로컬브랜치 생성 -> 원격저장소에 푸쉬)
  1.로컬브랜치 생성
$ git branch [만들고 싶은 브랜치명]
  2.원격저장소에 저장
$ git push origin [로컬에서 만든 브랜치명]

//브랜치 삭제하기
  1.로컬브랜치 삭제
$ git branch -d [삭제 브랜치명]
  2.원격저장소에 있는 브랜치 삭제
$ git push origin --delete [삭제 브랜치명]

//모든 브랜치 확인
$ git branch -v(-a)

 

[08] 반응형 웹 만들기


프로젝트 마감 전 D-2day에 모바일 최적화를 위해 반응형 웹에 시간을 다 쏟았다.

미디어 쿼리를 이용하여 모바일 화면 max-width: 48em에 맞추어 동일하게 진행했다.

오래걸릴 줄 알았던 작업이었는데 미디어 쿼리로 반응형웹 만드는게 생각보다 재밌었다.

 

@media screen and (max-width: 48em) {
    width: 100%;
    padding: 15vh 0 5vh;
    font-size: 1.75rem;

    h1 {
      font-size: 1.1em;
    }

    .camerapage_header {
      width: 80%;
    }
  } ;

 

[09] 모바일 시연 영상


 

[10] 엘리스 AI 트랙을 마무리하며...


6개월동안의 엘리스 AI 트랙 2기를 드디어 완주했다!

올해 초만해도 코딩이 뭔지도 모르고 있던 아무개였다.

엘리스라는 좋은 플랫폼에서 스타트를 끊을 수 있어서 다행이라는 생각이 들었다.

생활코딩 이고잉님을 첫 실시간 강의를 들을 때 마이크 꺼놓고 소리지르면서 연예인 보는 느낌이 들었었다 ㅋㅋ

 

git으로 협업하는 방법부터 시작해서, 알고리즘 강의,

그리고 내가 기대하던 프론트엔드 강의 html, css, JS, React

수업듣고 따라가기 어려웠지만 개인프로젝트를 진행하며 많이 익숙해진 Phthon, Flask,

데이터 분석 수업을 들으며 Numpy와 Pandas에 빠지기도 했고,

인공지능 수업을 들으며 CNN 이미지 처리 모델에 대해 공부해볼 수 있었다.

 

엘리스 덕분에 새로운 세계에 눈을 뜨게 된것 같다.

웹프로그래밍부터, 데이터분석, 인공지능까지 다양한 분야를 접해볼 수 있어서 좋았다.

프로젝트를 3번이나 진행해보면서 프론트엔드에 흥미를 느끼게 되었다.

사용자에게 눈으로 직접 보여줄 수 있고, 만들어낼 수 있는 매력에 빠지게 되었다.

또한 내가 다른사람과 함께 만들어내는 무언가가 있다는게 즐겁고 행복했다.