AWS 기술 블로그

Amazon Lookout for Vision과 Rekognition을 이용한 부품 결함 감지 시스템 구축하기

결함 감지 자동화 시스템 개발 배경

현재 제조 업계 인공지능 기술은 23억 달러의 시장 가치를 가지고 있으며, 이 가치는 2027년에는 163억 달러까지 성장할 것으로 예상됩니다. 오늘날 인공지능 기술 중 제조업에서 많이 사용하는 머신 비전은 대부분 PC 기반이지만, 머신 비전을 온프레미스 서버 또는 기계와 소프트웨어 통합형으로 사용하는 데에는 아직 많은 불편함이 있습니다. 고객은 머신 비전을 채택하는 동안 높은 비용, 기술적 어려움, ML 운영자/관리자 부족 등의 문제에 직면합니다. 이 글에서는 기존 온프레미스 머신 비전의 문제점을 해결할 수 있는 클라우드 기반의 머신 비전 기술을 제안하며, 특히 공정 품질 관리의 부품 검수 과정을 자동화할 수 있는 새로운 결함 검출 자동화 시스템을 소개합니다.

결함 감지 자동화 시스템 소개

개요

이 글에서 제안하는 결함 감지 자동화 시스템은 AWS의 서버리스 서비스를 이용해 공정 라인의 총체적인 품질 검수 과정을 구현합니다. 품질 검수 과정은 크게 두 단계로 나눌 수 있습니다. 바로 인라인 검사와 오프라인 검사입니다. 이 시스템에서는 산업용 컴퓨터 비전 서비스인 Amazon Lookout for Vision이 인라인 검사를 수행하며 사용자가 자유롭게 라벨을 생성할 수 있는 컴퓨터 비전 서비스인 Amazon Rekognition Custom Labeling이 오프라인 검사를 수행합니다. 추가로 공정 라인의 상태를 시각화하는 대시보드와 검사 결과를 저장하는 데이터베이스 또한 이 프로젝트에서 구현합니다.

Key Feature : 두 가지 머신 러닝 서비스의 결합

이 시스템에서 사용되는 산업용 컴퓨터 비전 서비스인 Amazon Lookout for Vision은 상품의 이미지를 분석해 생산품의 불량 보유 여부를 인라인에서 빠르게 판단합니다. 만약 검사한 생산품이 불량일 경우, Amazon Rekognition Custom Labeling 서비스는 생산품이 어떤 종류의 결함 유형을 가졌는지를 판단합니다. 이러한 병합 사용은 아래와 같은 고객의 비즈니스 요구 사항을 만족시킬 수 있습니다.

  1. 추후 작업상 손실을 줄이기 위해 결함품을 빠르게 인라인에서 제거할 수 있어야 한다.
  2. 결함품의 후처리를 위해 결함 유형의 자세한 분석이 필요하다.

또한 두가지 머신 러닝 서비스를 결합하여 사용하는 것이 단독 모델을 사용하는 것에 비해 전체적인 검사 품질을 약 10% 개선하는 효과가 있습니다.

목적

결함 감지 자동화 시스템은 운영 안정성과 분석 정확도를 높이는데 목적을 두고 있습니다.

서버리스를 이용한 운영 안정성 확보

이 시스템은 AWS의 서버리스 서비스를 이용하였습니다. 서비리스 서비스는 유저의 요청 변화에 따라 컴퓨트 리소스를 자동으로 조절 할 수 있습니다. 이러한 아키텍처를 통해 생산라인이 갑자기 증설되거나 품질 검사 대상이 늘어나는 일이 생기더라도 유연한 대응이 가능합니다.

2020년 진행되었던 IEEE의 International Symposium on Software Reliability Engineering 연구의 이미지 검사 배치 크기 증가 실험에 따르면, 배치 이미지 크기가 128개 까지 늘어났을 때, 온 프레미스 서버는 100초까지 응답 시간이 증가한 반면 AWS Lamda의 경우 이미지 배치 크기에 관계 없이 응답 시간이 0초에 가깝게 유지되었습니다. 이와 같은 서버리스의 특성은 시스템 전체에 확장성, 민첩성, 신뢰성을 제공하며 공장 인력이 시스템 유지와 관리가 아닌 생산에 집중할 수 있도록 도와줍니다.

기술 장벽을 줄여 검사 품질 정확도 근본적 향상

2022년 KOSIS의 인구 통계에 따르면, 공장 인력 중 기술 연구직은 8.3%에 불과합니다. 이런 기술 인력 부족 현장으로 인해 공정의 머신 러닝 업무는 주로 생산 직군 같은 도메인 전문가에 의해 이루어집니다. AWS는 머신 러닝의 전 과정을 웹 UI 기반의 콘솔로 다룰 수 있습니다. AWS의 콘솔을 통해 머신 러닝 비전문가들도 모델 성능 테스트 검사와 데이터 셋 업그레이드, 머신 러닝 모델 배포, 모델 버저닝, 새로운 결함 유형 추가 등의 작업을 할 수 있습니다. 해당 콘솔을 이용하면 도메인 전문가가 머신 러닝을 이용한 품질 검수 과정 전체를 이해할 수 있고, 이를 통해 검사 정확도를 높이기 위한 정확한 조치를 취할 수 있어 근본적인 품질 검사의 정확도 향상을 기대할 수 있습니다.

결함 감지 자동화 시스템 구현

지금부터 위 그림과 같은 서킷 보드 이미지를 가지고 결함을 검출하는 자동화 시스템을 구현 하겠습니다.

구현 단계 요약

  1. 머신 러닝 모델 학습시키기
  2. AWS Cloud Development Kit (CDK)로 결함 감지 자동화 시스템 클라우드에 배포하기
  3. Amazon QuickSight로 분석 결과 시각화하기

1. 머신 러닝 모델 학습시키기

1.1 S3 버킷에 서킷 보드 이미지 저장하기

AWS 콘솔 검색창에 S3를 찾아 클릭하고, 상단에 있는 Create bucket 버튼을 눌러 학습 이미지 저장을 위한 S3 버킷을 만듭니다.

git clone https://github.com/aws-samples/amazon-lookout-for-vision-rekognition-multiclassification.git

위의 명령어를 이용해 GitHub 저장소에 저장된 서킷 보드 예시 이미지를 다운받습니다.

aws s3 sync ./amazon-lookout-for-vision-rekognition-multiclassification/circuitboard-lkv s3://<YOUR_BUCKET_NAME>/circuitboard-lkv
aws s3 sync ./amazon-lookout-for-vision-rekognition-multiclassification/circuitboard-rekcl-augmented s3://<YOUR_BUCKET_NAME>/circuitboard-rekcl-augmented

위의 명령어를 이용해 S3로 서킷 보드 이미지를 복사합니다.

1.2 Amazon Lookout for Vision 모델 학습시키기

AWS 콘솔 검색창에 Amazon Lookout for Vision를 찾아 클릭한 후 메인 화면 왼쪽 메뉴에서 Projects를 찾아 클릭하여 새 Amazon Lookout for Vision 프로젝트를 생성합니다. 생성한 프로젝트 메인 화면 왼쪽의 Create dataset 버튼을 눌러 새로운 데이터 셋을 생성합니다.

Dataset configuration에서 Create a training dataset and a test dataset 옵션을 선택합니다.

Import images from S3 bucket을 선택한 후 새 창을 열어 학습 데이터 준비 과정에서 만들어 놓은 버킷의 circuitboard-lkv 폴더에 들어가 S3 URI를 복사합니다.

Automatic labeling 을 이용하기 위해 Automatically attach labels to images based on the folder name 체크박스를 선택하고 데이터 셋 생성을 완료합니다. 생성한 프로젝트의 데이터 셋 화면 오른쪽 상단의 Train model 버튼을 클릭하여 모델 트레이닝을 진행합니다.

Amazon Lookout for Vision의 경우 프로젝트 모델 콘솔의 Integrate API to the cloud 메뉴에서 모델 시작 명령어를 복사할 수 있습니다. 해당 명령어를 이용해 이미지 학습을 완료한 모델을 시작해주세요. CLI로만 모델 시작이 가능하며, 약 30분 정도 소요됩니다.

1.3 Amazon Rekognition 모델 학습시키기

콘솔 검색창에 Amazon Rekognition를 찾아 클릭한 후 메인 화면 왼쪽 메뉴에서 Use Cusom Labels 를 찾아 새 프로젝트를 생성합니다.

프로젝트 메인 화면 왼쪽의 Create dataset 버튼을 클릭한 후, Create a training dataset and a test dataset 옵션과 Import images from S3 bucket을 선택합니다. Dataset datails에서 Import images from S3 bucket을 선택하고 아래 두 가지 절차를 수행하고 데이터셋 만들기를 완료합니다.

  1. 데이터 셋을 가져오기 위해 버킷 주소 뒤에 circuitboard-rekcl-augmented/rekcl_train/을 붙여 적습니다. 테스트 셋의 경우 circuitboard-rekcl-augmented/rekcl_test/를 붙여 적습니다.
  2. Automatic labeling 을 이용하기 위해 Automatically attach labels to images based on the folder name 체크박스를 선택합니다.

생성한 프로젝트의 데이터 셋 화면 오른쪽 상단의 Train model 버튼을 클릭하여 모델 트레이닝을 진행합니다.

Amazon Lookout for Vision의 경우 프로젝트 모델 콘솔의 Integrate API to the cloud 메뉴에서 모델 시작 명령어를 복사할 수 있습니다. 해당 명령어를 이용해 이미지 학습을 완료한 모델을 시작해주세요. 모델 시작은 약 30분 정도 소요됩니다.

2. CDK로 결함 감지 자동화 시스템 클라우드에 배포하기

2.1 CDK 프로젝트 만들기

CDK 프로젝트를 만들기 위해서는 AWS Cloud9을 이용하거나 Local PC환경에 CDK 설치가 필요합니다. Local PC 환경 세팅은 이 링크를 참고해주세요. 아래 두 명령어를 이용해 빈 디렉토리를 만들고 CDK 템플릿을 이용해 typescript를 사용하는 CDK 프로젝트를 생성합니다.

mkdir defect-detection && cd defect-detection
cdk init sample-app --language typescript

2.2 CDK 메인 stack 작성하기

lib/defect-detection-stack.ts의 모든 내용을 삭제한 후 아래의 코드를 붙여넣습니다.

import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import { DynamoEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as iam from "aws-cdk-lib/aws-iam";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as cdk from "aws-cdk-lib";
import * as fs from "fs";
import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks";
import * as s3n from "aws-cdk-lib/aws-s3-notifications";
import * as sfn from "aws-cdk-lib/aws-stepfunctions";

export class DefectDetectionStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    //env injection
    const rekogARN = this.node.tryGetContext("rekogARN");
    const l4vNAME = this.node.tryGetContext("l4vNAME");
    const l4vVER = this.node.tryGetContext("l4vVER");

    //DynamoDB creation
    const resultTable = new dynamodb.Table(this, "DetectResult", {
      partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
      stream: dynamodb.StreamViewType.NEW_IMAGE,
    });

    //start S3 bucket
    const startBucket = new s3.Bucket(this, "imageBucket");

    //result storing S3 bucket
    const resultBucket = new s3.Bucket(this, "resultBucket");

    //firehose
    const firehoseRole = new iam.Role(this, "firehoseRole", {
      assumedBy: new iam.ServicePrincipal("firehose.amazonaws.com"),
    });

    const firehose = new CfnDeliveryStream(this, "firehoseStreamToS3", {
      deliveryStreamName: "firehose-delivery-stream",
      deliveryStreamType: "DirectPut",
      s3DestinationConfiguration: {
        bucketArn: resultBucket.bucketArn,
        compressionFormat: "UNCOMPRESSED",
        encryptionConfiguration: {
          noEncryptionConfig: "NoEncryption",
        },
        prefix: "user-logs",
        errorOutputPrefix: "user-error-logs",
        roleArn: firehoseRole.roleArn,
      },
    });

    //lambda definition
    // 1. Lambda for Lookout for Vision
    const detectAnomaliesLambda = new lambda.Function(this, "DetectLambda", {
      code: new lambda.InlineCode(
        fs.readFileSync(
          "lambda/DetectAnomaliesFunction/startDetectAnomalies.py",
          { encoding: "utf-8" }
        )
      ),
      handler: "index.lambda_handler",
      timeout: cdk.Duration.seconds(30),
      runtime: lambda.Runtime.PYTHON_3_9,
      environment: {
        PROJECT_NAME: l4vNAME,
        MODEL_VERSION: l4vVER,
      },
    });

    // 2. Lambda for Rekognition
    const classifyDefectsLambda = new lambda.Function(this, "classifyLambda", {
      code: new lambda.InlineCode(
        fs.readFileSync("lambda/DetectAnomaliesFunction/classifyDefects.py", {
          encoding: "utf-8",
        })
      ),
      handler: "index.lambda_handler",
      timeout: cdk.Duration.seconds(30),
      runtime: lambda.Runtime.PYTHON_3_9,
      environment: {
        PROJECT_ARN: rekogARN,
      },
    });

    // 3. Lambda for storing result
    const putResultInDBLambda = new lambda.Function(this, "putDBLambda", {
      code: new lambda.InlineCode(
        fs.readFileSync("lambda/DetectAnomaliesFunction/putItemInDynamoDb.py", {
          encoding: "utf-8",
        })
      ),
      handler: "index.lambda_handler",
      timeout: cdk.Duration.seconds(30),
      runtime: lambda.Runtime.PYTHON_3_9,
      environment: {
        DYNAMODB_TABLE_NAME: resultTable.tableName,
        REGION: resultTable.env.region,
      },
    });

    // 4. Lambda for DB to firehose data delivery
    const DynamoToFirehoseLambda = new lambda.Function(
      this,
      "DynamoToFirehoseLambda",
      {
        code: new lambda.InlineCode(
          fs.readFileSync("lambda/DynamoDbToFirehose/lambda_function.py", {
            encoding: "utf-8",
          })
        ),
        handler: "index.lambda_handler",
        timeout: cdk.Duration.seconds(30),
        runtime: lambda.Runtime.PYTHON_3_9,
        environment: {
          FirehoseName: "firehose-delivery-stream",
        },
      }
    );

    //step function definition
    const DetectAnomalies = new tasks.LambdaInvoke(
      this,
      "detectAnomaliesLambda",
      { lambdaFunction: detectAnomaliesLambda, outputPath: "$.Payload" }
    );
    const classifyDefects = new tasks.LambdaInvoke(this, "ClassifyDefects", {
      lambdaFunction: classifyDefectsLambda,
      outputPath: "$.Payload",
    });
    const putResult = new tasks.LambdaInvoke(this, "putResult", {
      lambdaFunction: putResultInDBLambda,
      outputPath: "$.Payload",
    });

    //create chain
    const choice = new sfn.Choice(this, "IsAnomaly?");
    const skip = new sfn.Pass(this, "pass");
    choice.when(
      sfn.Condition.booleanEquals("$.DetectAnomalyResult.IsAnomalous", true),
      classifyDefects
    );
    choice.when(
      sfn.Condition.booleanEquals("$.DetectAnomalyResult.IsAnomalous", false),
      skip
    );
    choice.afterwards().next(putResult);
    const definition = DetectAnomalies.next(choice);

    //create state machine
    const stateMachine = new sfn.StateMachine(this, "stateMachine", {
      definition,
      timeout: cdk.Duration.minutes(5),
    });

    const startStateMachineLambda = new lambda.Function(this, "startLambda", {
      code: new lambda.InlineCode(
        fs.readFileSync(
          "lambda/DetectAnomaliesFunction/startStateMachineExecution.py",
          { encoding: "utf-8" }
        )
      ),
      handler: "index.lambda_handler",
      timeout: cdk.Duration.seconds(30),
      runtime: lambda.Runtime.PYTHON_3_9,
      environment: {
        STATE_MACHINE_ARN: stateMachine.stateMachineArn,
      },
    });

    //lambda service execution role
    detectAnomaliesLambda.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ["lookoutvision:*"],
        resources: ["*"],
      })
    );

    classifyDefectsLambda.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ["rekognition:*"],
        resources: ["*"],
      })
    );

    DynamoToFirehoseLambda.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ["dynamodb:*"],
        resources: ["*"],
      })
    );

    DynamoToFirehoseLambda.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ["firehose:*"],
        resources: ["*"],
      })
    );

    DynamoToFirehoseLambda.addEventSource(
      new DynamoEventSource(resultTable, {
        startingPosition: lambda.StartingPosition.TRIM_HORIZON,
      })
    );

    putResultInDBLambda.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ["dynamodb:*"],
        resources: ["*"],
      })
    );

    //lambda state machine execution role
    classifyDefectsLambda.grantInvoke(stateMachine.role);
    detectAnomaliesLambda.grantInvoke(stateMachine.role);
    putResultInDBLambda.grantInvoke(stateMachine.role);
    stateMachine.grantStartExecution(startStateMachineLambda);

    //lambda bucket & DB execution role
    resultTable.grantReadWriteData(DynamoToFirehoseLambda);
    startBucket.addEventNotification(
      s3.EventType.OBJECT_CREATED,
      new s3n.LambdaDestination(startStateMachineLambda)
    );
    startBucket.grantReadWrite(startStateMachineLambda);
    startBucket.grantReadWrite(classifyDefectsLambda);
    startBucket.grantReadWrite(detectAnomaliesLambda);
    resultBucket.grantWrite(firehoseRole);
    resultBucket.grantPut(firehoseRole);
    resultBucket.grantPut(DynamoToFirehoseLambda);
    resultBucket.grantReadWrite(DynamoToFirehoseLambda);
  }
}

아래 Lambda 함수 폴더를 다운받고 압축을 해제한 후 defect-detection 폴더의 루트 디렉토리에 추가합니다.

cdk bootstrap 명령을 터미널에 입력해 부트스트랩 스택을 설치합니다.

cdk bootstrap

2.3 CDK 아키텍처 배포하기

stack에서 사용하는 아래 세 가지 환경변수를 AWS 콘솔에서 찾습니다. Amazon Rekognition의 경우 프로젝트의 use model 탭에 ARN이, Amazon Lookout for Vision의 경우 프로젝트 대시보드에서 프로젝트 이름과 모델 버전을 확인할 수 있습니다. 또한 머신 러닝 모델 start는 training이 완전히 끝난 후 진행할 수 있으며, 모델 start이후에도 모델 이용까지 약 30분이 더 소요됩니다.

  • rekogARN : 사용하고자 하는 Rekognition model의 ARN
  • l4vNAME : 사용하고자 하는 Lookout for Vision 프로젝트 이름
  • l4vVER : 사용하고자 하는 Lookout for Vision model 버전

cdk deploy 명령을 사용해 스택을 배포합니다. 아래 세 가지 context를 반드시 변경해서 진행해주세요.

cdk deploy --context rekogARN=<YOUR_REKOGNITION_PROJECT_ARN> --context l4vNAME=<YOUR_L4V_PROJECT_NAME> --context l4vVER=<YOUR_L4V_MODEL_VERSION>
  • YOUR_REKOGNITION_PROJECT_ARN : Rekognition model ARN (arn으로 시작)
  • YOUR_L4V_PROJECT_NAME : Lookout for Vision 프로젝트 이름
  • YOUR_L4V_MODEL_VERSION : Lookout for Vision model 버전 (e.g – Model 1의 경우 뒤의 숫자 1만 지정)

콘솔 검색창에 CloudFormation을 찾고, DefectDetectionStack의 Resources탭에서 총 31개의 리소스에 대해CREATE_COMPLETE가 되었는지 확인합니다.

2.4 이미지 결함 검출 자동화 시스템 작동 확인하기

콘솔 검색창에 S3를 찾아 클릭합니다. 버킷 목록 중 defectdetectionstack-imagebucket으로 시작하는 S3 버킷을 찾아 들어갑니다. 1.1에서 다운받은 서킷 보드의 이미지 중 검사를 진행하고 싶은 서킷 보드 이미지를 버킷에 넣습니다. 이미지를 S3 버킷에 넣으면 StepFunction이 자동으로 동작을 시작해 머신 러닝을 이용한 결함 검사를 시작합니다. 콘솔 검색창에 StepFunctions를 찾고, stateMachine으로 시작하는 State machine을 클릭합니다.

Executions 탭에서 lambda를 이용해 circuit board 이미지 결함을 분석하는 트랜잭션을 볼 수 있습니다.

각 Execution을 클릭하면 circuit board 이미지 결함을 분석하는 자세한 과정을 볼 수 있습니다.

콘솔 검색창에 DynamoDB를 찾고, 메인 화면에서 DefectDetectionStack-DetectResult로 시작하는 테이블을 선택합니다.

테이블 내부 화면 상단 오른쪽에 Explore table items 버튼을 클릭합니다.

결함 분석 결과가 저장되어있는 것을 볼 수 있습니다.

3. Amazon QuickSight로 분석 결과 시각화하기

AWS의 Business Intelligence (BI) 대시보드인 Amazon QuickSight 를 이용해 결함 분석 결과를 간단히 시각화합니다.

3.1 Amazon QuickSight 결함 검출 로그 엑세스 허용하기

콘솔 검색창에 Amazon QuickSight를 찾아 클릭한 후, QuickSight access to AWS service 영역에서 Amazon S3 체크 후 Select S3 buckets를 클릭합니다. 버킷 목록 중 결함 판정 결과가 들어있는 defectdetectionstack-resultbucket을 선택해 Amazon QuickSight의 엑세스를 허용합니다.

3.2 manifest.json 파일 만들기

아래 manifest.json 템플릿의 <YOUR_S3_URI>에 defectdetectionstack-resultbucket으로 시작하는 S3 버킷의 user-logs2022 폴더 URI를 복사합니다.

//manifest.json
{
  "fileLocations": [
      {
          "URIPrefixes": [  "<YOUR_S3_URI>"]
      }
  ],
  "globalUploadSettings": {
      "format": "JSON"
  }
}

3.3 Amazon QuickSight 분석 결과 시각화하기

Amazon QuickSight의 Datasets메뉴 오른쪽 상단의 New dataset 버튼을 클릭하고 S3 옵션을 선택합니다.

Data source name을 입력한 후 앞서 제작한 manifest.json 파일을 업로드한 후 Connect 버튼을 클릭합니다

Visualize 버튼을 눌러 데이터 시각화를 시작합니다.

데이터셋 왼쪽의 Field와 Visual type들을 조정해 결함 정보에 대한 여러 그래프를 만들 수 있습니다.

다음과 같이 분석 결과를 통해 서킷 보드의 결함 유형을 한 눈에 확인할 수 있습니다.

Step-by-Step guide workshop

같은 내용을 담은 더 자세한 가이드는 이 워크샵 링크에서 확인하실 수 있습니다.

결론

이 글에서는 AWS의 컴퓨터 비전 머신 러닝 서비스인 Amazon Lookout for Vision과 Amazon Rekognition 서비스를 이용해 공정의 품질 관리 과정을 자동화하는 시스템을 소개했습니다.

이 프로젝트는 기존 온 프레미스 환경에서 해결하기 어려웠던 품질 관리 정확도 상승에 기여할 수 있는 클라우드 활용 방안을 모색하는 데에서 시작했습니다. 이 시스템과 AWS의 Root cause analysis (RCA) 서비스와의 결합을 통해 차후 공정의 근본적인 문제를 찾기 위한 설계 또한 고려해볼 수 있습니다.

Seungwon Choi

Seungwon Choi

최승원 어소시에이트 솔루션즈 아키텍트는 고객이 최적의 솔루션을 선택하여 비즈니스 성과를 달성할 수 있도록 고객과 함께 효율적인 아키텍처를 구성하는 역할을 수행하고 있습니다.