반도체

반도체 8대 공정 중 마지막 package 공정을 후공정이라 한다.

단어 선택

  • 1번
    • Back End Process of: 반도체 후공정 패키징 과정 전체
    • Post-processing stage: 그 과정이 완료된 후에 이루어지는 후속 처리
  • 2번
    • Consequently: 공식적, 필연적
    • As a result: 예상외의 것
  • 3-1번
    • Required: 법률, 규정, 지침 등의 명확한 규제 또는 기준에 의해 요구되는 경우
    • Necessary: 논리적, 실직적, 사실적으로 필요한 상황일 경우
  • 3-2번
    • the user must manually input the new parameters: 강한 필수성
      • 기계에 전달되는 방식이 여러 가지일 수 있는 상황에서
    • the user must enter the new parameters manually: 강한 필수성
      • 키보드나 터치스크린 등을 통해 데이터를 직접적으로 시스템에 제공하는 행위에서
  • 4번
    • Accordingly: 제시된 정보나 상황에 따라 적절한 대응을 강조
    • In response: 특정한 요청이나 변화에 따른 행동이나 의견
    • Therefore: 논리적인 결론을 도출할 때 사용
  • 5번
    • we propose a new semiconductor defect detection user interface that minimizes user parameter input and can flexibly respond to material changes: 능동태(일반 적인 글쓰기)
    • A new semiconductor defect detection user interface capable of minimizing user parameter input and flexibly responding to material change is proposed: 수동태(과학적 논문이나 공식적인 보고서에서 많이 사용)
  • 6번
    • "to incorporate various insights from actual field experts through user requirement analysis”: 왜 재구성 되었는가? 현장 전문가들의 통찰력을 결합
    • "by reflecting various opinions of actual field experts through analysis of user needs": 왜 재구성 되었는가? 현장 전문가들의 의견이 인터페이스에 반영하다. (빈약)
  • 7번
    • "The user interface developed through this design allows efficient task performance even when new material changes or new field experts are introduced due to its fewer user inputs and intuitive logical flow.": 직접적인 주장
      • allows: 가능성이나 기회를 제공
    • "This user interface, developed through this design process, ensures efficient task performance, even when new material changes or new field experts are introduced, by applying less user input and an intuitive logical flow.": 개발 과정 강조
      • ensures: 강력한 결과를 보장
    • "The user interface developed through this design applies less user input and an intuitive logical flow to efficiently perform tasks even when new changes in materials or new field experts are introduced.": 어떻게 효과적으로 작업을 수행하는지 초점
  • 9번
    • "Looking ahead, the user interface proposed in this study is flexibly designed to enable continuous improvements by introducing more user-friendly elements, including voice recognition and chatbot functionalities.": 앞을 내다보는 느낌으로 연속적인 개선을 가능하게 하는 유연한 디자인으로 만들어졌다는 것을 강조
    • "In this study, the proposed user interface is flexibly designed to perform continuous improvement by introducing more user-friendly elements based on voice recognition and chatbot in the future.": 지속적인 개선을 강조
    • "The user interface proposed in this study is flexibly designed to enable continuous improvements by introducing more user-friendly elements based on voice recognition and chatbot in the future.": 첫 번째와 세 번째 문장의 혼합입니다. 첫 번째 문장과 같이 유연한 디자인과 연속적인 개선을 가능하게 하는 능력을 강조

 

작성에 도움을 받은 글

https://blog.essayreview.co.kr/academic/common-transition-terms-used-in-academic-papers/

'Project > Vision Inspection GUI' 카테고리의 다른 글

C# OpenCVSharp4 Troble Shooting  (0) 2023.07.23
C# 클래스 -> 구조체 변경 Troble Shooting  (0) 2023.07.19

프로젝트 진행 도중 학습시킨 AI모델을 어떻게 적용 시키는게 좋을까에 대한 고민 과정 기록

상황

Restfull으로 개발된 SpringBoot 애플리케이션에 비즈니스 로직을 구현해두고

특정 비즈니스 로직을 실행시키기 위해서는 AI 모델에 대한 코드와 데이터에 대한 전처리 코드를 사용해야 한다.

즉 Java 코드안에서 python 스크립트를 호출하여 사용해야 하는 상황

해당 python 코드는 AI 모델이 사용되기 때문에 상당히 많은 라이브러리를 요구하며 복잡하다.

 

선택지

고민에대한 선택지를 제공해준 기사: https://www.baeldung.com/java-working-with-python

  • ProcessBuilder 사용
  • JSR-223 스크립팅 엔진 작업 (jython)
  • PythonInterpretor 클래스
  • Apache Common Exec 사용
  • 상호 운용성을 위해 HTTP 활용 (Django)

 

ProcessBuilder 사용  ✅

자바의 ProcessBuilder API를 사용하여 네이티브 운영 체제 프로세스를 생성하여 파이썬을 실행하고 스크립트를 실행시키는 방법

  • Python 및 필요한 라이브러리 설치: EC2 인스턴스에 Python이 설치되어 있어야 하며, 코드에서 사용하는 라이브러리(예: matplotlib, torch, torchvision 등)도 설치되어 있어야 한다.
  • GPU 지원: 코드에서 CUDA를 사용하고 있으므로, GPU가 있는 EC2 인스턴스를 사용하거나, CUDA가 없는 경우 코드를 수정하여 CPU만 사용하도록 설정해야 한다.
  • ProcessBuilder 사용: Java의 ProcessBuilder를 사용하여 파이썬 스크립트를 실행할 수 있습니다. 이 경우, 파이썬 스크립트는 EC2 인스턴스에 존재해야 하며, ProcessBuilder는 이 스크립트를 실행하는 데 사용됩니다.
@Test
public void givenPythonScript_whenPythonProcessInvoked_thenSuccess() throws Exception {
    ProcessBuilder processBuilder = new ProcessBuilder("python", resolvePythonScriptPath("hello.py"));
    processBuilder.redirectErrorStream(true);
    Process process = processBuilder.start();
    List<String> results = readProcessOutput(process.getInputStream());
    assertThat("Results should not be empty", results, is(not(empty())));
    assertThat("Results should contain output of script: ", results, hasItem(
        containsString("Hello Baeldung Readers!!")));
    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}

 

JSR-223 스크립팅 엔진 작업 ✖️

JSR-223 스크립팅 엔진을 사용하여 jython을 사용하여 JVM 위에서 코드를 실행

https://www.jython.org/index

  • Jython 이슈사항: Jython은 Python 2.7까지만 지원하기 때문에 Python 3의 기능을 사용할 수 없다. 그 밖에도 AI모델을 돌리기 위한 Pythorch 라이브러리를 사용하는 것이 불가능 따라서 해당 상황에서는 부적합하다.

 

아파치 커먼즈 실행 

Apache Commons Exec를 사용하여 Java에서 외부 프로세스를 실행시켜 사용한다.

  • Python 및 필요한 라이브러리 설치: EC2 인스턴스에 Python이 설치되어 있어야 하며, 코드에서 사용하는 라이브러리(예: matplotlib, torch, torchvision 등)도 설치되어 있어야 한다.
  • GPU 지원: 코드에서 CUDA를 사용하고 있으므로, GPU가 있는 EC2 인스턴스를 사용하거나, CUDA가 없는 경우 코드를 수정하여 CPU만 사용하도록 설정해야 한다.
  • executor.execute() 사용: Java에서 외부 프로세스를 실행시켜 사용한다. 프로세스의 파라미터 관리, 실행 결과 처리 관리, 프로세스 실행 시간 제어를 하는 것이 가능하다
  • 아파치 커먼즈 이슈사항: Jython은 Python 2.7까지만 지원하기 때문에 Python 3의 기능을 사용할 수 없다. 그 밖에도 AI모델을 돌리기 위한 Pythorch 라이브러리를 사용하는 것이 불가능 따라서 해당 상황에서는 부적합하다.
CommandLine cmdLine = new CommandLine("python");
cmdLine.addArgument("/path/to/your/python/script.py");
DefaultExecutor executor = new DefaultExecutor();
int exitValue = executor.execute(cmdLine);

 

상호 운용성을 위해 HTTP 활용  

Django 프레임워크를 사용하여 추가로 서버를 하나더 개발하여 프로그램을 동작시킨다.

프론트에서 Ai기능이 필요한 순간마다 Django 서버로 요청을 보내고 얻은 결과물을 스프링 서버에서 사용하여 동작 시킨다.

  • Django 프레임워크 설치: python에 Django 프레임 워크를 사용한다.
  • 컨트롤러 코드 작성: AI모델이 판별할 데이터를 보내는 컨트롤러 코드(urls.py)를 작성한다.
  • 서버 배포: Spring과 다른 컴퓨터에서 배포시킨다.

 

현재의 선택, 이유

상호 운용성을 위해 HTTP 활용 하는 방향으로 추진하였다.

1. 확장성: 데이터의 전처리 과정등의 추가적인 로직이 올라 갈 수 있다는 미래 가능성 염두하였다.

2. 분산처리: 프로그램의 핵심적인 로직이기 때문에 스프링의 로직과 분리하여 적용하는 것이 효율적이라고 생각하였다.

3. 유연성, 효율성: 결국 목표는 python과 java에대한 언어의 통합인 상황에서 HTTP는 언어의 독립적인 프로토콜이기 때문에 매개로 하여 언어의 통합을 이루어 낼 수 있으며 각 언어의 장점을 최대한으로 활용 할 수 있기 때문

 

차후 추가 검증이 필요한 부분

1. 복수의 기기가 접근 할 때의 각 방식의 성능 테스트

  • ProcessBuilder나 Apache Commons Exec를 사용하지 않은 이유는
  • 실행시켜야 하는 python 코드가 여러 라이브러리를 사용해서 상당히 복잡하므로 코드의 복잡성과 성능면에서 불리 할 거라는 추측 때문이기 때문에 검증을 최소한 한 번은 해볼 필요가 있다 판단된다.

 

의약품의 이름 정보를 쿼리에 담아 요청을 보내면 해당 하는 알약의 정보를 반환 받는 기존의 로직을
수강한 강의 영상을 바탕으로 개선한 과정 기록 

기존 로직

  • controller에 비지니스 로직이 섞여 있는 모습 
package _PF026.DrDrug.controller;

import _PF026.DrDrug.dto.MedicationDto;
import _PF026.DrDrug.dto.ResponseDto;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

@RestController
public class DrugOpenAPIController {
    private final static String BASE_URL = "http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList";
    private final String API_KEY = "your key"
		
		@GetMapping(value = "/MedicationDto/{itemName}", produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<MedicationDto> getDrugInfo(@PathVariable String itemName){
        DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
        factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
        String encodedItemName = URLEncoder.encode(itemName, StandardCharsets.UTF_8);
        WebClient webClient = WebClient.builder()
                .uriBuilderFactory(factory)
                .baseUrl(BASE_URL)
                .build();

        Mono<ResponseDto> response = webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .queryParam("serviceKey", API_KEY)
                        .queryParam("pageNo", "1")
                        .queryParam("numOfRows", "1")
                        .queryParam("entpName", "")
                        .queryParam("itemName", encodedItemName)
                        .queryParam("itemSeq", "")
                        .queryParam("efcyQesitm", "")
                        .queryParam("useMethodQesitm", "")
                        .queryParam("atpnWarnQesitm", "")
                        .queryParam("atpnQesitm", "")
                        .queryParam("intrcQesitm", "")
                        .queryParam("seQesitm", "")
                        .queryParam("depositMethodQesitm", "")
                        .queryParam("openDe", "")
												.queryParam("updateDe", "")
                        .queryParam("type", "json")
                        .build())
                .retrieve()
                .bodyToMono(ResponseDto.class);

        return response.flatMap(this::convertToMedication);
    }
    private Mono<MedicationDto> convertToMedication(ResponseDto responseDTO) {
        List<MedicationDto> medicationDtos = responseDTO.getBody().getItems();
        if (medicationDtos != null && !medicationDtos.isEmpty()) {
            return Mono.just(medicationDtos.get(0));
        } else {
            return Mono.empty();
        }
    }
}

Rest API 방식으로 url/MedicationDto/{itemName} 으로 요청을 보내면

itemName에 해당 하는 의약품 정보를 공공포털 api를 이용하여 get 요청을 보내 의약품에 대한 정보를 얻는다.

얻은 정보를 전용 Dto 객체로 파싱하여 저장하고 다른 계층에 적합한 Dto 객체로 다시 변환하여 전송한다.

 

1차 개선

  • SOLID 원칙에서(S) Single Principle Responsibility 원칙에 따라서
  • 한 클래스는 하나의 책임을 가져야 한다.
  • 공공포털 조회 기능을 service 로직으로 분리
package _PF026.DrDrug.service;

import _PF026.DrDrug.dto.MedicationDto;
import _PF026.DrDrug.dto.ResponseDto;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Service
public class DrugOpenAPIService {
    private final static String BASE_URL = "http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList";
    private final String API_KEY = "your api key";

    public Mono<MedicationDto> getDrugInfo(String itemName){
        DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
        factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
        String encodedItemName = URLEncoder.encode(itemName, StandardCharsets.UTF_8);
        WebClient webClient = WebClient.builder()
                .uriBuilderFactory(factory)
                .baseUrl(BASE_URL)
                .build();

        Mono<ResponseDto> response = webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .queryParam("serviceKey", API_KEY)
                        .queryParam("pageNo", "1")
                        .queryParam("numOfRows", "1")
                        .queryParam("entpName", "")
                        .queryParam("itemName", encodedItemName)
                        .queryParam("itemSeq", "")
                        .queryParam("efcyQesitm", "")
                        .queryParam("useMethodQesitm", "")
                        .queryParam("atpnWarnQesitm", "")
                        .queryParam("atpnQesitm", "")
                        .queryParam("intrcQesitm", "")
                        .queryParam("seQesitm", "")
                        .queryParam("depositMethodQesitm", "")
                        .queryParam("openDe", "")
                        .queryParam("updateDe", "")
                        .queryParam("type", "json")
                        .build())
                .retrieve()
                .bodyToMono(ResponseDto.class);

        return response.flatMap(this::convertToMedication);
    }

    private Mono<MedicationDto> convertToMedication(ResponseDto responseDTO) {
        List<MedicationDto> medicationDtos = responseDTO.getBody().getItems();
        if (medicationDtos != null && !medicationDtos.isEmpty()) {
            return Mono.just(medicationDtos.get(0));
        } else {
            return Mono.empty();
        }
    }
}
  • 컨트롤러 코드 수정
package _PF026.DrDrug.controller;

import _PF026.DrDrug.dto.MedicationDto;
import _PF026.DrDrug.service.DrugOpenAPIService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
public class DrugOpenAPIController {
    private final DrugOpenAPIService drugOpenAPIService;

    @Autowired
    public DrugOpenAPIController(DrugOpenAPIService drugOpenAPIService) {
        this.drugOpenAPIService = drugOpenAPIService;
    }

    @GetMapping(value = "/MedicationDto/{itemName}", produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<MedicationDto> getDrugInfo(@PathVariable String itemName){
        return drugOpenAPIService.getDrugInfo(itemName);
    }
}

 

2차 개선

  • SOLID 원칙에서(D) Dependency Inversion Principle 원칙에 따라서 다시 수정
  • 구현체가 아닌 인터페이스에 의존함으로써 결합력을 낮추고 응집력을 높힐 수 가 있다.
  • 변경된 컨트롤러
package _PF026.DrDrug.controller;

import _PF026.DrDrug.dto.MedicationDto;
import _PF026.DrDrug.service.DrugService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
public class DrugOpenAPIController {
    private final PublicApiService publicApiService;

    @Autowired
    public DrugOpenAPIController(DrugService drugService) {
        this.publicApiService = drugService;
    }

    @GetMapping(value = "/MedicationDto/{itemName}", produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<Object> getDrugInfo(@PathVariable String itemName){
        return publicApiService.getDrugInfo(itemName);
    }
}
  • 추가되고 변경된 service 로직
package _PF026.DrDrug.service;

import reactor.core.publisher.Mono;

public interface PublicApiService {
    Mono<Object> getInfo(String query);
}
package _PF026.DrDrug.service;

import _PF026.DrDrug.dto.MedicationDto;
import _PF026.DrDrug.dto.ResponseDto;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Service
public class DrugOpenAPIService implements PublicApiService {
    private final static String BASE_URL = "http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList";
    private final String API_KEY = "your api key";

    public Mono<Object> getDrugInfo(String itemName){
        DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
        factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
        String encodedItemName = URLEncoder.encode(itemName, StandardCharsets.UTF_8);
        WebClient webClient = WebClient.builder()
                .uriBuilderFactory(factory)
                .baseUrl(BASE_URL)
                .build();

        Mono<ResponseDto> response = webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .queryParam("serviceKey", API_KEY)
                        .queryParam("pageNo", "1")
                        .queryParam("numOfRows", "1")
                        .queryParam("entpName", "")
                        .queryParam("itemName", encodedItemName)
                        .queryParam("itemSeq", "")
                        .queryParam("efcyQesitm", "")
                        .queryParam("useMethodQesitm", "")
                        .queryParam("atpnWarnQesitm", "")
                        .queryParam("atpnQesitm", "")
                        .queryParam("intrcQesitm", "")
                        .queryParam("seQesitm", "")
                        .queryParam("depositMethodQesitm", "")
                        .queryParam("openDe", "")
                        .queryParam("updateDe", "")
                        .queryParam("type", "json")
                        .build())
                .retrieve()
                .bodyToMono(ResponseDto.class);

        return response.flatMap(this::convertToMedication);
    }

    private Mono<MedicationDto> convertToMedication(ResponseDto responseDTO) {
        List<MedicationDto> medicationDtos = responseDTO.getBody().getItems();
        if (medicationDtos != null && !medicationDtos.isEmpty()) {
            return Mono.just(medicationDtos.get(0));
        } else {
            return Mono.empty();
        }
    }
}

 

3차 개선

  • Lombok 라이브러리를 사용하여 controller 코드 간결화
  • @RequiredArgsConstructor를 사용하여 빈으로 등록된 클래스를 이용해 의존성 주입
    • 이 어노테이션은 클래스 내부에 final 또는 @NonNull이 붙은 필드에 대해 생성자를 자동으로 생성 
    • 생성자가 명시적으로 존재하지 않아도, Lombok 라이브러리가 컴파일 시점에 생성자를 추가해준다.
    • 따라서 @Autowired 어노테이션이 없어도 스프링이 자동으로 의존성 주입을 해준다.
@RestController
@RequiredArgsConstructor
public class DrugOpenAPIController {
    private final PublicApiService drugOpenAPIService;

    @GetMapping(value = "/MedicationDto/{itemName}", produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<Object> getDrugInfo(@PathVariable String itemName){
        return drugOpenAPIService.getInfo(itemName);
    }
}
  • 해당 코드만 봐서는 구현체가 어떤 클래스로 주입받았는지 생략이 되어 판별하기가 쉽지않다, 그 판별 원리는 다음과 같다.
    • 우선적으로 스프링 컨테이너가 해당 타입의 빈을 찾는다. (위에서는 PublicApiService 타입)
    • 해당 타입의 빈이 하나만 있다면 그 빈을 주입한다.
    • 여러개의 빈이 있다면 빈의 이름(메소드 이름, @Bean 어노테이션의 이름 속성)을 사용하여 빈을 선택 
    • 만약 일치하는 이름의 빈이 없다면 NoUniqueBeanDefinitionException 발생
  • @Primary 어노테이션을 사용하면, 동일한 타입의 빈이 여러 개 있을 때 기본적으로 선택되는 빈을 지정할 수 있다.
  • @Qualifier 어노테이션을 사용하면, 동일한 타입의 빈이 여러 개 있을 때 주입될 빈을 명시적으로 지정할 수 있다.

 

코드 관련 링크

https://suhanlim.tistory.com/222

https://suhanlim.tistory.com/110

도움을 받은 강의 링크

좋은 지식 공유해주셔서 감사합니다 김영환 멘토님

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

한이음 프로젝트 개설 신청 후 승인까지의 과정을 기록하고 만약에 신청하고자 하는 사람이 있다면 도움이 될 것 같은 내용을 자극이 개인적 생각으로 작성하였으므로 틀린 정보가 있을 수도 있습니다.

ICT 멘토링이란?

2004년부터 시작된 ICT멘토링제도 운영 사업으로 과학기술정보통신부가 지원하고
정보통신기획평가원(IITP)가 주관하는 인력양성사업으로 대학생(멘티)이 ICT기업전문가(멘토)와 팀을 이루어
프로젝트를 수행함으로써 ICT실무 역량을 향상 시키는 대한민국 ICT분야 대표 멘토링 프로그램

멘토가 개설한 프로젝트에 참여 신청하여 참가하는 방법 or 멘티가 스스로 프로젝트를 개설하고 사람을 모으는 방법 두 가지가 존재

경쟁률

프로보노: 2.8:1

한이음 종합: 2.86:1

혜택

  • 상당한 양의 고품질의 유데미 유료 강의 무료 수강 가능
  • GitLab 지원
    • GitLab 핸드온 교육 및 강의 제공  
  • 팀 지원금 130만원
    • 클라우드, 애뮬레이터, 특허 등록, 앱 등록, 회의 공간 대여 등등 지원 명목  

추천하는 대상 

프로젝트를 진행해본 경험이 없는 사람 (멘토님의 도움)

긴 기간동안 공부하면서 프로젝트를 만들고자 하는 사람 (부담되는 강의들을 무료로 수강가능)

컴퓨터공학과 2~3학년 학생 (모를 때 일수록 부딪쳐 봐야 얻는게 많은 시기)

교내에서 it창업을 준비하는 팀 (팀 지원금 활용)

 0.  ICT 멘토링을 알게 된 경로

지금까지 학교 공부는 열심히 해왔지만 아무런 개발 지식도 개발 경험도 개발 방법도 모르는 상태인 나는 이대로는 안되겠다는 생각이 들었고 스스로 개발자로서 성장하고 싶어 제일 좋은 방법이 무엇인지 학교 선배님께 질문하게 되었다.

그렇게 해서 들은 답변은 지금부터 라도 아무거나 좋으니까 신청해서 가급적이면 팀을 이끄는 리더로 도전해보라는 말씀이었다. 

그렇게 내가 참여할 수 있는 활동을 알아보던 중 학교 동아리 선배님들의 추천으로 ICT 멘토링에 대해 알게 되었다.

현재 속한 학교 교내 동아리에서 주기적으로 신청하여 참여하는 거의 1년 단위의 프로젝트 였고 개발을 하기 위한 역량이 부족한 현재의 나에게는 공부하면서 프로젝트를 만들만한 좋은 기회라고 생각되어 관심을 가지게 되었다. 

 

1.  프로젝트를 개설하다

처음에는 내가 관심이 가고 기여할 수 있는 주제로 멘토님들이 개최한 프로젝트를 찾아 신청하려고 했으나,

SpringBoot 기술을 공부해서 적용시키기 위한 프로젝트도 몇 개 존재하지 않았고 그마저도 신청한 사람들과 경쟁이 안되는 상황이었다. 

냉정하게 생각했을 때 아무런 노베이스 초보자를 데리고 가기보다는 몇 번의 프로젝트 경험으로 능숙한 사람들을 데리고 가는게 맞으니까...

결론은 내가 프로젝트를 개설하는 수 밖에 없단 거였다.

어떤 주제로 프로젝트를 개설해야 할지 정말 많은 고민을 했었는데 결론은

현재 내 주변에 처한 문제점을 해결하는 주제로 자리를 잡게 되었다. (이 부분이 계획서를 작성하는데 가장 도움이 된 점이라 생각한다)

분야와 프로젝트명 (팀이름 DrDrug)

 

2.  멘토님, (교수님) 선정(팁)

같이 하기로 내가 모집한 팀원들과 프로젝트를 진행하면서 우리를 이끌어주실 멘토님을 뽑는 과정 
개인적으로는 프로젝트를 설계하면서 가장 어떻게 해야 할지 감이 안 잡히는 부분에 대한 조언을 구할 수 있는 분으로 교수님
프로젝트의 진행 에자일 방식의 스프린트 계획을 도와주실 분으로 멘토님을 선정하는 게 베스트라고 생각한다.
화려한 스펙을 가지신 분이 도움이 되긴 하지만 무조건 좋은 사람이다. 라는 일방적인 추천으로 뽑는 건 추천하지 않는다. 

 

3.  프로젝트 수행 계획서 작성(팁, 예시)

  • 개조식으로 작성할 것
  • 프로젝트 흐름도 이미지를 만들어 추가 할 것
  • 프로토타입 프로젝트 이미지를 만들어 추가 할 것

 

공모전 관련 정보

  • 수상이 목적이라면 기술에 집중할 필요가 있다.
  • 만약 빨리 만들고 빨리 배포가 목표라면 심사위원에게 직접 어필해야 한다.
  • 심사위원: 교수, 기술사, 기업체임원 40대 남성, 완고, 고지식, 공익적인 주제(선호)
  • 작년도에 했던 주제로 그대로 다시 도전하는 팀들이 상당히 있다.
  • 팀당 지원금은 약 130만원

전반적 일정/상황 (수상 목표시)

  • 3월 목표: 3/31까지 계획서 제출
  • 4월 중순 (결과 발표) 중간고사
  • 5월 목표: 개발환경 세팅 R&R 완료
  • 6월 기말고사
  • 7월 목표: 설계서 제출
  • 8월 목표: 왠만한 기능은 구현이 끝나야 함
  • 9월 목표: 공모전 마무리 + 보강
  • 10월 목표: 대면 평가 준비
  • 11월 말 : 엑스포 + 한이음 종료

승인 발표 후 후기

승인까지 도움이 될 수 있었던 건 아래와 같은 내용 덕분인 것 같다.

1. 현재 내 주변에 처한 문제점을 해결하는 주제로

계획서와 설계서를 작성할 때 why? 로 물어서 effect? 가 무엇인지 작성해야 하는 내용이 대부분이기 때문에

확실한 이유가 없다면 쓰기 많이 난감하였다.

2. 구체적인 설계를 포함

아무래도 실제 비용을 지원해주고 긴 기간 동안 프로젝트가 진행되다 보니 정말 해당 프로젝트를 진행할 의지가 있는지를 중요하게 보는 것 같은데 해당 여부를 구체적인 설계를 토대로 판단하는 것 같다.

 

성장한 점

Before: 일반적인 웹, 앱 프로젝트를 구조를 알지 못했고 어떠한 상호 작용을 거치면서 애플리케이션이 동작하는지 이해가 안 되는 상태

After: 캡스톤 디지안 포스터나 발표를 봤을 때 어떤 구조로 어떤 흐름으로 애플리케이션이 동작하는지 이해가 되고 설계가 가능

 

솔직히 말하면 프로젝트를 구상하기엔 부족한 사람이 추상적으로 계획을 짜고 실행한 거라고 생각한다. 

직접 프로젝트를 계획하고 설계하는 입장이 되어 보니 본인 스스로 엄청나게 공부하고 선배님들께 질문하여 지식을 공유받음으로써 
이제 일반적으로 웹 앱 프로젝트가 어떻게 동작하고 각기의 계층이 상호작용하는지 이해할 수 있었고 나 스스로 설계가 가능하게 되었다.

상황

일하고 있는 곳 에서 반도체 불량 부분을 OpenCvSharp4 라이브러리를 사용하여 표시하는 일을 맡게 되었다.

OpenCvSharp4 라이브러리의 Circle, Rectangle 함수를 사용하여 이미지를 그리는 코드를 작성하였는데 회색조 이미지에는 색상 표기가 되는 반면 윈도우 화면을 캡쳐 하여 만들어둔 일반적인 이미지에서는 검은색으로만 표시가 되는 문제가 발생하였다.

 

추측 

처음에는 이미지를 그리기 전에 OpenCV에서 색상을 나타내는 방식(B,G,R)과 .NET에서 색상을 나타내는 방식(R,G,B)이 다르기 때문에 이러한 문제가 발생한 것이라 생각을 하여 변환하는 코드를 작성하였으나  

        private static Scalar ToScalar(Color color)
        {
            return new Scalar( color.B, color.G, color.R);
        }

해당 코드를 사용했음에도 문제가 해결이 되지 않아 고심하던 중

이미지의 속성에 차이가 있지 않을까? 라는 생각이 문득 들게 되었고 

정답이었다. 

색상이 잘 표시되는 이미지의 비트수준은 24비트 흔이 알고 있는 (RGB)형식이었고 3채널

색상이 표시되지 않는 이미지의 비트수준은 32비트 (RGB+투명도A)형식이었다 4채널

해결

따라서 이미지를 그리기 전에 OpenCVSharp4의 라이브러리를 사용하여 이미지 타입을 24비트로 변환하여 작업을 하니 문제를 해결 할 수 있었다.

            if (Img.Channels() == 4)
            {
                Img = Img.CvtColor(ColorConversionCodes.BGRA2BGR);
            }

'Project > Vision Inspection GUI' 카테고리의 다른 글

논문 초록 작성 고민  (1) 2023.07.31
C# 클래스 -> 구조체 변경 Troble Shooting  (0) 2023.07.19

상황

일하고 있는 곳 에서 반도체 불량 검출 GUI제작 프로젝트를 진행하던 중 C#에서 검사에 사용하기로 한 파라미터들의 정보를 저장 해둔 클래스를 구조체로 바꾸어서 작업을 해야 하기로 이야기가 나오게 되었고  

클래스 -> 구조체로 바꾸는 작업을 다른 팀원이 하고 프로그램을 돌려본 결과 기존에 마우스 조작 이벤트로 잘 그려지던 (Roi)관심 영역 바운딩 박스가 그려지지 않는다는 이슈사항이 발생하였다.

 

<기능이 제대로 수행되는 상황>

<클래스 -> 구조체 이슈 이후>

처음에 마우스 클릭으로 좌표가 잡히긴 하지만 드레그 이벤트가 아예 동작하지 않았다.

 

또 내가 코드를 어디 잘 못 짰겠구나 싶어 디버깅 하며 원인을 찾던 중 로직에서 문제가 될 것 이라 추정되는 부분을 찾을 수 있었다.

<문제가 일어난 걸로 추측되는 코드>

        private void UpdateRoi(MouseEventArgs e, List<Roi> roiList, List<Roi> calibratedRoiList)
        {
            if (roiList.Count == 0) return;

            int mouseX = Math.Min(e.X, PictureBox.Width);
            int mouseY = Math.Min(e.Y, PictureBox.Height);

            Roi lastRoi = roiList[roiList.Count - 1];
            Roi lastCalibratedRoi = calibratedRoiList[calibratedRoiList.Count - 1];

            lastCalibratedRoi.Width = (int)(mouseX * WidthRatio) - lastCalibratedRoi.TopLeft.X;
            lastCalibratedRoi.Height = (int)(mouseY * HeightRatio) - lastCalibratedRoi.TopLeft.Y;

            lastRoi.Width = mouseX - lastRoi.TopLeft.X;
            lastRoi.Height = mouseY - lastRoi.TopLeft.Y;

        }

로직 자체는 빈틈이 없다 생각되어 C#의 구조체와 클래스의 어떠한 차이로 발생한 이슈가 아닌가 추측이 들었고,

해당 내용을 공부하니 원인을 찾을 수 있었다.

그림 출처: https://woozzang.tistory.com/29

즉 위에 그림기준으로 생각하면 원래 아래 존재하는 코드는 Heap 메모리의 주소를 받아 사용하여 값이 바뀌게되면 Call By Reference 참조의 의한 호출이기 때문에 실제 값 또한 바뀌었을 것 인대

Roi lastRoi = roiList[roiList.Count - 1];
Roi lastCalibratedRoi = calibratedRoiList[calibratedRoiList.Count - 1];

구조체로 바뀌게 되어 Stack 영역에는 실제 값이 저장되게 되어 Call By Value 값의 의한 호출이 되어 해당 값을 바뀌더라도 원본의 값이 변경되지 않게 되었다.

 

즉 List에 C# 클래스가 저장될 때는 참조를 저장하기 때문에 위에 코드에서 참조된 값을 수정하여도 아무 문제가 일어나지 않지만

C# 구조체가 저장될 때는 참조를 저장하는 것이 아닌 값을 저장하는 것이기 때문에 이러한 문제가 발생한 것 이었다.

<수정된 코드>

값을 직접 바꾸어 주도록 로직을 수정하였다.

        private void UpdateRoi(MouseEventArgs e, List<Roi> roiList, List<Roi> calibratedRoiList)
        {
            if (roiList.Count == 0) return;

            int mouseX = Math.Min(e.X, PictureBox.Width);
            int mouseY = Math.Min(e.Y, PictureBox.Height);

            Roi lastRoi = roiList[roiList.Count - 1];
            Roi lastCalibratedRoi = calibratedRoiList[calibratedRoiList.Count - 1];

            lastCalibratedRoi.Width = (int)(mouseX * WidthRatio) - lastCalibratedRoi.TopLeft.X;
            lastCalibratedRoi.Height = (int)(mouseY * HeightRatio) - lastCalibratedRoi.TopLeft.Y;

            lastRoi.Width = mouseX - lastRoi.TopLeft.X;
            lastRoi.Height = mouseY - lastRoi.TopLeft.Y;

            roiList[roiList.Count - 1] = lastRoi;
            calibratedRoiList[calibratedRoiList.Count - 1] = lastCalibratedRoi;
        }

'Project > Vision Inspection GUI' 카테고리의 다른 글

논문 초록 작성 고민  (1) 2023.07.31
C# OpenCVSharp4 Troble Shooting  (0) 2023.07.23

 

해당 프로젝트를 실행하기전 필자는 방학 동안 React와 Spring 맛보기 공부 찍먹을 했었고

이를 바탕으로 이번년도 동안 공부하면서 준비한다면 웹을 만드는 프로젝트를 도전해볼만 하다는 생각이 들었다.

그렇게 하여 학교 지도교수님을 섭외하고 피드벡을 받게 되었는데

왠 걸  교수님께서 지금 준비하려는 프로젝트는 기능적인 요소에서 카메라를 사용해야 하므로 웹이 아닌 네이티브 앱으로 만드는 고민을 해보라고 하셨다.

 

여기서 필자는 1차 멘붕이 었다. 

방학동안 웹은 조금이나마 공부했지만 앱은 처음 듣는 소리 였기 때문이다.

 

그리하여 앱에 대해 공부를 시작하게 되었고

앱이 총 4가지 종류로 나뉨을 알 수가 있었다.

  • 모바일 웹: 모바일 환경의 사용자 분들을 대상으로 변경한 웹
    • 카메라 같은 핸드폰 기본 고유 기능을 활용할 수 없거나 사용이 힘듬
  • 네이티브 앱: IOS, 안드로이드 별도의 운영체제를 대상으로 만들어진 원시형태의 앱
    • 각 운영체제의 모든 기능을 가져다가 쓸 수 있고 퍼포먼스 적으로 성능이 띄어난 앱을 만들 수 있음
  • 하이브리드 앱(웹뷰): 외관만 IOS, 안드로이드로 꾸며놓고 그 안에 웹페이지를 띄우는 형식
  • 크로스 플랫폼 앱: 하나의 언어로 모든 플렛폼에 대응가능한 앱
    • 여기에 웹페이지를 띄우는 방식을 추가하면 크로스 플랫폼겸 하이브리드 앱이 됨 

여기서 필자가 처음 기획했을 때는 모바일 웹 형태로 만들기 고안했었고 휴대폰의 카메라 기능을 활용하기 위해선 지도교수님의 조언대로 네이티브 앱으로 만드는게 좋다는 생각을 할 수가 있었다.

 

이제 어떤 형태로 서비스를 만들 것인지는 결정이 되었는데 이제 이것 어떤식으로 구현할 것 인가?

에대한 고민을 해야되는 시기가 왔고

 

프론트: React, 백: Spring -> 프론트: Kotlin, 백: SpringBoot 

으로 계획을 변경하되 웹은 Spring에서 페이지 째로 넘겨주는 방식으로 동작하는 걸 알고 있었지만 웹이랑은 어떤 상호작용을 거치는 지를 알지 못 했다.

 

얇은 배경지식으로 맨땅에서 시작하는 나는 열심히 학교 선배님들과 졸업하신 현업자 선배님께 질문을 구하거나 열심히 구글링 하면서 Rest API 라는 개념에 대해 알게 되었고

일단 rest api는 웹이든 앱이든 url mapping을 해놓으면 그걸 rest api라고 불러 ㅎ 또 웹역시 json데이터만 서버에 요청이 가능해 ajax , axios , fetch에 대해 알아보렴 ㅎ 네가말한것중 웹에 대응하는 서버는 jsp, html,ejs등 웹 템플릿을 같이 넘겨주는 방식이 10에 9할이구 , 앱에 대응하는 서버는 오직 data만 넘겨준다는 내용은 맞아 ㅎ

 

본질적으로 앱이 서버를 필요로 하는 이유는 필요한 데이터를 데이터 베이스에서 꺼내오기 위해서 임을 생각하고

해당 요청을 서버에게 보내고자 할 때 대응되는 데이터를 RestAPI를 통해 보내고 서버는 Mapping 되는 함수에 기능에 따라 DB에서 해당하는 데이터를 가지고 JSON 형식으로 프론트에게 응답을 해주는 일련의 과정을 생각 할 수 있었다.

 

이제야 머릿속에 그림이 그려지기 시작해서 

덕분에 해당 프로젝트에서 백을 담당 할 경우 어떤 부분을 공부해야 하는지 명확히 알 수 있었다.

 

일단은 Spring을 통해 DB의 데이터를 가지고 오는 방식에 대해 공부해야 했고 

그래서 해당 강좌를 돈을 써서 결제해서 들어보긴 하였는데 아무래도 방학동안 Spring의 기본 개념을 탄탄히가 아니라 절반정도 찍먹한 방식으로 강의를 듣다보니 이해가 되지 않는 부분이 많아서  

처음부터 다시 시작한다는 마음으로 인프런 에서 백엔드 스터디 코스를 차근차근 밝기로 했다. 

 

+ Recent posts