[68호]한국 디스플레이 산업전시회
한국 디스플레이 산업전시회
글 | 이규연 기자 press@ntrex.co.kr
국내 유일의 디스플레이 전문 전시회인 한국디스플레이 산업전시회(IMID 2021)가 지난 8월말 서울 코엑스에서 3일간 개최됐다.
이번 전시회의 목적은 세계 1위 디스플레이 산업국의 위상을 확고히 할 수 있는 국내 최대의 전시로 최신 정보 교류 및 기술의 동향을 파악하고 디스플레이 시장 현황에 대한 정보 교환의 장을 마련하는 것이다.
주요 전시 품목으로는 디스플레이 관련 장비 및 설비, 디스플레이 관련 재료 및 부품, OLED, LCD, Wearable, Flexible, Micro Display 등이 있다.
㈜노바센트릭스는 미국 텍사스에 본사를 두고 있으며, 최첨단 기술로 전자인쇄 제조를 선도하는 기업이다. 이번 전시회에 선보인 제품은 포토닉큐어링을 위한 연구 개발용 장비인 PulseForge이다.
포토닉큐어링은 플래시램프의 빛을 사용하여 얇은 필름을 열처리하는 공정을 뜻한다. PulseForge는 첨단 포토닉큐어링 기술을 구현할 수 있고 다양한 공정 파라미터 조건 설정이 가능하며, 사용하기 쉬운 멀티 터치스크린 유저페이스는 사용자에게 최소의 시간으로 소재 및 공정 최적화를 달성할 수 있는 환경을 제공해 준다.
또한 광경화 기술을 제공하고 기능성 잉크 및 박막을 단 몇 밀리 세컨드 내에 고열 처리할 수 있으며, 이때 기판의 온도는 낮게 유지되어 손상되지 않는다. 이 장비는 폴리머나 종이와 같이 열에 약판 기판 위에 놓인 박막 물질을 건조시키거나 소결, 어닐링 하는데 사용이 된다.
UV 처리를 통해 Metalon, ICI-Series 와 같은 구리 산화물 환원 잉크 물질의 반응을 발생시키고 조절할 수 있으며, 금속, 비금속 및 반도체 기반의 잉크뿐 아니라 PVD 금속, 금속 산화물 등의 넓은 증착 레이어를 처리할 수 있다.
PulseForge는 이중 안전 기능으로 설계 및 제작되었고 내부에는 다양한 데이터 포트가 내장되어 있다. 뿐만 아니라 계측기를 위한 신호 처리 기능이 있어서 사용자는 편리하게 이용할 수 있다.
㈜동우화인켐은 양자점 재료, 플렉서블 디스플레이 제조에 사용되는 다양한 재료를 제조 및 개발하는 초기술집약적 기업이다. 이번 전시회에서 주력 제품인 반도체용 케미칼과 터치센서를 선보였다.
동우화인켐의 터치센서는 최고의 생산기술과 품질을 자랑하며 외작 업체로는 세계 1위의 점유율을 기록하고 있다. 멀티 터치와 내구성이 우수한 정전 용량 방식의 터치센서는 표면을 터치할 때 생기는 미세한 정전용량의 변화를 감지하여 터치 위치를 계산하는 방식으로 모바일 제품에 널리 적용되고 있는 터치 구동방식이다.
프로세스 케미칼분야에서 동우화인켐은 자체 개발에 성공하여 초 극미량 분석 노하우와 첨단 설비를 바탕으로 차별화된 품질을 제공하고 있다.
반도체용 케미칼은 반도체 공정에서 자외선 빛을 이용하여 회로를 Wafer에 전사시킬 때 빛의 조사 여부에 따라 감응함으로써 미세회로 패턴을 형성할 수 있도록 하는 노광 공정용 감광재료이다. 동우화인켐에서는 Nano-Level에서 고해상력을 요구하는 DRAM, Flash Memory, Logic Device 생산에 사용되는 Resist로 Metal, Implant, Critical Layer에 대응 가능한 품목으로서 각각의 Device Target에 부합하는 Novolak 형인 g&i-Line Resist와 반도체의 최소 선폭을 더욱 미세화하기 위한 KrF, ArF Resist를 생산 공급하고 있다.
㈜브러쉬뱅크는 LCD 및 반도체용 세정 브러쉬를 개발하는 기업이다. 이번 전시회에서는 벨트 타입의 브러쉬와 채널 타입의 브러쉬를 선보였다.
벨트 타입 브러쉬는 채널 형태로 제작되지 않기 때문에 SKIP 현상이 발생하지 않아 LCD 세정에 많은 장점이 있다. 모의 분포가 일정하고 고밀도이며, 정확한 스펙 관리가 가능할 뿐만 아니라 특히 무게 경감과 녹 방지 및 세정력에 탁월한 강점을 보인다.
채널 타입은 장축의 샤프트를 중공으로 제작하여 떨림과 휨이 없고 무게 중심의 치우침이 없다. 채널 안쪽 개구부의 모재 삽입 공간을 넓혀 모의 밀도를 극대화하여 최적의 세정 능력을 제공하고 개구부 안쪽 및 양 옆쪽에서 발생되는 녹의 원인을 제거하여 최적의 크린도를 유지시켜준다. 단 채널 간의 간격으로 인해 세정 시 SKIP 현상이 발생될 수 있으니 유의해야 한다.
브러쉬뱅크의 브러쉬는 PVA 스펀지를 사용하여 제작하는데 이 스펀지는 오픈 셀로 구성이 되어 부피의 90% 이상이 기공으로 형성되어 있다. 또한 자기 자중의 10배에 해당하는 물을 흡수, 보수할 수 있고 세정 대상물에 손상 및 스크래치를 발생시키지 않는 장점이 있다.
내마모성, 내약품성이 뛰어나서 정밀 세정이 요구되는 세정 공정에서 브러쉬뱅크의 제품은 많은 관심을 끌 것으로 예상된다.
㈜넥센서는 표면 형상(OLED, 글라스, 필름, 가공면 등) 및 높이에 대한 3D 측정 및 검사 솔루션을 제공하는 기업이다.
넥센서의 주력 제품은 포인트 측정 범위를 대면적으로 확대하여 적용하는 대면적 간섭계 모듈과 재질의 특성이 다르거나 표면의 거칠기가 일정하지 않은 제품의 경우에도 기술적으로 극복하여 광범위한 측정이 가능하도록 개발한 두께 측정 센서, 그리고 3D 형상 측정 모델, 경면처럼 대상체가 반사되거나 반짝이는 제품에 대한 검사가 가능한 자유 곡면 측정기가 있다.
반도체, 디스플레이 제품의 높이와 단차, 두께 등을 측정할 수 있는 X1-2는 Wafer bump, Glass 표면 형상, Pattern 단차, Camera VCM 등 실시간으로 측정이 가능하다.
X1-3은 Wafer 표면 거칠기, 디스플레이 디세돌기 및 형상, OLED 단층 박막 측정 및 분리가 가능한 제품이고 검사 속도는 1초 이하의 시간이 걸린다.
XP-1은 One shot 측정으로 경면과 투명 재질 표면의 형상을 측정할 수 있다. 한 장의 영상을 획득하여 표면 형상 측정이 가능하고 Wafer와 Film 종류가 측정 대상이다. XF-3은 자유 곡면의 표면 형상 및 표면 돌기와 다양한 경면과 투명한 표면의 형상도 측정 가능하다. Warpage 및 Curl에 대한 측정 데이터로 정량적인 관리 또한 가능한 제품이다.
XV-1 제품은 코팅, 글라스, 웨이퍼, 이차전지 실링의 두께를 측정하는 제품이고 최대 16개의 프로브를 장착하여 동시 측정이 가능하다, 나노미터급 정확도를 갖추어 변위 및 두께의 오차 범위를 최소화시킬 수 있고 제품의 층이 존재할 경우에는 분리하여 측정할 수도 있다.
㈜네프코는 반도체 및 디스플레이 등의 여러 분야에 포토리소그라피 기술을 응용하는 공정에서 미세회로를 형상화하여 마스터 포토마스크를 공급하는 기업이다.
포토마스크란 반도체 칩, 디스플레이 패널, 인쇄회로 기판 등의 제품에 회로를 새겨 넣기 위해서 사진의 원판 역할을 하는 것이다. 포토마스크는 원재료인 Blank Mask에 패턴을 새겨 넣어 만들어지는데 Blank Mask 위에는 감광을 위한 PR(Photo Resist)이 박막으로 도포 되어 있다.
PR의 타입은 Positive와 Negative 타입이 있으며 포토마스크에는 주로 Positive 타입의 PR이 사용된다. Positive PR은 노광 시 빛을 받으면 분자들이 반응하여 이격 되는 현상이 발생하고 현상 과정에서 빛을 받은 부분의 PR이 제거된다. 이번 전시회에서 네프코는 Chrome Mask와 Emulsion Mask를 선보였다.
Chrome Mask는 반도체 직접회로 구조의 패턴 형상을 크롬이 도포된 석영유리 또는 소다라임 등에 형성한 것으로 가시광선 자외선, X선 등을 이용한 노광 장비에 사용되며 레지스트의 미세한 상을 표현하는 능력, 일반적으로 라인 앤 스페이스 상을 형성할 수 있는 최소의 선 또는 공간의 폭을 나타내는 능력이 우수하다.
Emulsion Mask는 할로겐화 은이 도포된 소다라임을 사용하여 패턴을 형상한 것으로, 노광 시 빛을 받은 부분이 현상액에 용해되지 않고 패턴으로 형성되는 Negative 타입이며 라인 앤 스페이스 상을 형성할 수 있는 선 또는 공간의 폭을 나타내는 능력이 우수한 제품이다.
㈜드림은 로봇을 활용한 스마트 팩토리 솔루션을 제공하는 기업이다. 이번 전시회에서 잉크젯 통합 분석기인 JetXpert를 선보였다.
JetXpert는 완전 통합형 분석 시스템으로 하나의 기기로 잉크 방울 및 프린트 헤드 분석 및 조건 설정을 하여 동일 조건의 인쇄 테스트, 인쇄 결과, 이미지 분석 등 잉크젯 통합 분석이 가능한 장비이다. 이 제품은 장점으로는 모든 프린트 헤드와 사용이 가능하다.
JetXpert는 프린트 헤드의 분사 신호를 사용하여 스트로브를 동기화하고 이미지 캡처를 작동시켜 프린트 헤드 마운팅 장치로도 사용이 가능하다. 손쉬운 보정 절차와 방울 분석 및 데이터 수집을 위해 직관적인 사용자 인터페이스를 사용하고 공간이 협소한 실험실 및 생산 환경에서 가동할 수 있도록 설치 공간을 최소화하여 편의성을 극대화시켰다. 그뿐만 아니라 고객 맞춤형 OEM 제작도 가능하다.
제품의 성능으로는 통계 데이터를 수집할 수 있고 실시간으로 분사 성능 측정과 기록까지 할 수 있으며, 탭 구분 텍스트 파일로 데이터를 보관할 수 있다. 픽셀당 1미크론의 해상도에서 375ns로 단일 액적 이미징을 하여 단일 스트로브 이벤트 기능을 할 수 있다.
JetXpert의 측정 항목으로는 속도, 부피, 궤도, 리거먼트의 길이, 위성 방울의 부피를 측정할 수 있다.
㈜씨티에스는 미세먼지 세정용 초음파 건식 세정기 전문 제조 기업이다. 씨티에스의 제품에서는 물리적 장치에 의한 초음파 발진이 아니라 공기의 흐름을 통제하고 제어하는 방식으로 초음파 에어를 형성하기 때문에 일반 에어 세정에서 제거가 불가능한 미세 이물을 제거할 수 있다.
초음파 세정기 제품 종류는 패널 단면 세정기, 패널 양면 세정기, 모바일용 패널 세정기, 스마트폰 카메라 렌즈 세정기가 있다.
패널 단면 세정기는 패널을 USC에 공급하고 Stage에 진공 흡착한 후 상면을 세정하도록 설계되었다. 적용 패널의 두께는 최소 0.3mm 이상이 되어야 초음파 공기에 의한 비접촉 방식으로 세정이 가능하다.
패널 양면 세정기는 패널을 Conveyor로 이동하며 상면, 하면 동시에 세정하도록 한다. 적용할 수 있는 산업에는 OLED, LCD, SOLAR, 특수 철강 등이 있다.
모바일용 패널 세정기는 인쇄 전, 후의 소형 패널을 롤러에 의해 자동 투입해서 패널의 상면, 하면을 동시에 세정할 수 있다. 이 제품은 흡착 진공 Table이 불필요하여 협소한 공간에도 설치가 가능하다.
스마트폰 카메라 렌즈 세정기는 스마트폰 제조 공정 중 렌즈 부품에 문제 되는 미세 이물을 초음파 Nozzle로 비접촉하여 세정한다. 이때 초음파 Nozzle의 세정력은 실리카 파우더 99% 이상 제거할 수 있는 힘을 가졌다.
㈜필옵틱스는 OLED 디스플레이 산업분야에서 최첨단 공정 장비를 제조 및 판매하는 기업이고 세계 최초로 Flexible OLED 용 Laser Cutting 장비 및 Lift Off 장비, 전기차 용 이차전지 공정 장비를 개발하여 공급하고 있다. 이번 전시회에서는 3D 검사기 장비를 선보였다.
3D 검사기인 Argos G100은 Nano Meter Level의 고속 측정 시스템을 이용한 2D 스캔 방식으로 각 픽셀의 경사도를 이용하여 프로파일을 생성한다. 대면적 검사에 적합한 표면 형상 검사 방식으로 설계되었고 다중 카메라를 이용한 대면적 대응과 응용 분야에 따라 확장이 가능한 빠른 검사 시스템이다.
Argos G100은 양산 장비 적용이 가능한 빠른 측정 속도(0.8초 이하)를 가지고 있고, 제조 장비에 부착해서 사용할 수 있는 확장성과 소프트웨어 인터페이스를 탑재하고 있다. 진동, 광노이즈, 신호 간섭 등 외부 환경에 최소 영향을 받는 우수한 반복성과 고정밀 FPD(Flat Panel Display) 양산 환경에서 검증된 검사 시스템, 제품 표면 굴곡에 대한 낮은 민감도로 Flexible 제품 대응성을 향상시켜 외부 환경 및 노이즈에 대한 신뢰성을 높일 수 있다.
Argos N200은 현미경 이상의 고해상도 검사 시스템 장비이다. Argos G100과 마찬가지로 2D 스캔 방식이며, 현미경을 대체할 때 사용하면 알맞다. 가볍고 작아서 빠른 측정이 가능하고 제품 표면 굴곡에 대한 낮은 민감도와 DOF(Depth of Field)로 초점 유지 능력이 우수한 장비이다.
(주)원에스티는 Ball 및 Roller를 전동체로하는 직선운동과 회전운동 베어링을 전문으로 개발 및 생산하는 기업으로 국내 최초로 LM Shaft와 Ball Spline을 국산화했다. 이러한 기술을 토대로 Linear Motion System을 반도체, 디스플레이 제조 및 검사 장비, 산업용 로봇, 공작기계 분야에 공급하고 있다. 이번 전시회에는 Linear Motion 가이드를 선보였다.
Linear Motion 가이드의 S 시리즈는 표준 타입과 스페이서 리테이너 타입으로 나뉜다. 표준 타입에는 콤팩트형과 플랜지형으로 나뉘는데 콤팩트형은 블록 상면에 탭 가공이 되어 있으며 블록의 폭과 높이와 길이를 최소화한 슬림형 콤팩트 타입이며 4열 써큘러 구조로 볼 접촉각이 45° 인 4방향 등하중 타입이다. 플랜지형은 블록의 플랜지부에 탭 가공이 되어 있으며 블록의 폭과 높이를 최소화한 슬림형 타입이며 4열 써큘러 구조는 콤팩트형과 동일하다.
㈜신성이엔지는 고효율 청정 시스템, 최적 에너지 공조 시스템, 플랜트 엔지니어링 및 정밀 시공 시스템 분야에서 발전을 거듭하여 반도체, 화학, 나노, 2차 전지 등 국내외 다양한 산업에 최적화된 System Technology Solution을 제공하는 기업이다.
이번 전시회를 통해 신성이엔지의 클린환경사업부는 파티클 가시화 시스템 및 스마트 팩토리 솔루션을 선보였다.
신성이엔지는 사람 중심의 미래형 스마트 팩토리를 강조하며 생산활동의 모든 정보(4M 1E)를 실시간으로 수집/분석하여 품질 및 생산실적 예측을 하고 자원을 재분배하여 생산 효율을 극대화 시킨 공장을 소개했다. 현장에 있는 MES System은 생산 실적을 실시간으로 동기화시키고 모니터링할 수 있으며, 작업 내역을 추적하여 상태를 파악하고 제어할 수 있다. 또한 품질 데이터에 대한 분석과 불량 관리도 가능하여 편리하다.
전시회에 선보인 기류 연동 시스템 장비는 미세 기류 감지, 방향 표시 기능이 있는데. 방향이 녹색으로 이동되면 정상이고 적색 방향으로 이동되면 비정상 수치를 나타낸다. 주 사용처는 FFU/EFU 작동 및 장비의 기류 방향을 실시간으로 감시할 때 사용된다.
㈜휴비츠는 광학기술, 기계기술 및 전자 네트워크 기술을 바탕으로 눈을 측정/진단하고 렌즈를 가공하는 안광학 의료기기를 공급하는 기업이다. 1999년 국내 최초로 자동 검안기를 출시하여 세계 3대 메이커로 성장하였다. 이번 전시회에서는 OCT 검사기인 3D ATI HMT와 HST 시리즈를 소개했다.
OCT는 빛의 간섭성을 이용하여 단층을 촬영하는 기술이다. 광원은 Beam Splitter를 통해 Reference 방향과 Sample 방향으로 분기 되며, Sample에 반사되어 나오는 신호와 기준 값인 Reference Mirror에서 반사되어 오는 신호의 간섭 정보로 이미지를 생성한다.
먼저 휴비츠의 3D ATI 장비는 안광학 단층 촬영 OCT 기술로 점 하나 놓치지 않고 완벽한 검사가 가능하다. 3D ATI HMT 장비는 초정밀 검사 장비에 적용할 수 있도록 소형, 경량 설계되었다.
고객이 원하는 스타일에 따라 형상 및 사이즈, 마운트의 위치 설정 등의 커스터마이징도 가능해서 검사 분야의 특성에 맞게 제품별 조합으로 최적의 검사 조건을 제공한다. 또한 센서는 조합 및 확장이 가능하여 측정 범위를 확장하고 보이지 않은 숨은 영역까지 촬영을 할 수 있다.
3D ATI HST 장비는 PC 내장형 스마트 단층 스캐너로 최적화된 소프트웨어가 임베디드 되어 측정 및 분석이 용이하며 Stand-Alone 타입으로 공간 효율성도 우수하다. X, Y, Z 축의 이동과 15도 틸팅이 가능한 전동 모듈은 여러 각도에서 측정이 가능하여 샘플 변환이 많은 환경에서도 빠르고 유연하게 측정한다. 또한 뛰어난 광학 설계로 까다로운 어플리케이션까지 정밀하고 신속하게 처리가 가능하다는 장점이 있다.
삼성디스플레이는 이번 전시회에서 혁신적인 Foldable OLED 제품을 선보였다.
특히 ECO SQUARE 기술 개발을 통해 기존 폴더블 제품 대비 색재현력은 증가하고 소비전력을 절감하였으며 UPC(Under Panel Camera) 기술로 카메라를 내재화하며 진정한 Infinity Display를 경험할 수 있어 전에 없던 새로운 사용성을 소비자들에게 제공할 수 있게 되었다.
또한, 폴더블 디스플레이를 통해 스마트폰 디스플레이 크기의 한계에 도전하여 간편성과 휴대성을 모두 잡아 5G 시대에 최적화를 성공하였다. 폴더블 제품과 더불어 OLED 노트북 제품 또한 삼성디스플레이의 혁신 제품이다. 스마트폰으로만 가능한 OLED의 장점을 노트북까지 영역을 확대하였는데 화려한 색 표현력은 물론 빠른 응답속도, 슬림한 디자인으로 더 얇고 가벼운 디스플레이를 구현하여 이동의 편의성을 주었다.
LG디스플레이는 Cinematic Sound 8K OLED 제품과 투명 OLED 제품을 선보였다. 디스플레이 패널에서 직접 소리를 내는 88인치 8K 패널로 최상의 홈 시네마를 구성할 수 있는 TV와 55인치 투명 OLED 패널과 침대가 결합하여 스마트 라이프를 꿈꾸게 하는 침대는 신혼부부에게 유혹이 될 만한 제품이였다.
LG디스플레이는 금번 IMID 2021 전시회를 발판 삼아 지속적으로 Cinematic Sound OLED와 8K OLED, Flexible OLED 등 혁신적인 신기술을 개발하겠다고 밝혀 앞으로의 행보가 기대된다.
국내 유일의 디스플레이 전문 전시회인 한국디스플레이 산업전시회(IMID 2021)가 지난 8월 25일부터 27일까지 성황리에 마무리되었다.
국내 유수 기업들의 월등한 기술력을 바탕으로 한 고도화된 장비와 혁신적인 장비 전시는 관람객들에게 풍성한 볼거리를 제공한 전시회였다. 이번 전시회를 통해 세계 디스플레이 시장에서 1위를 달리는 이유를 확인할 수 있는 시간이었으며, 앞으로도 디스플레이 시장에서 굳건히 1위 자리를 지키길 바라면서 이번 관람기를 마친다. DM
[68호] 스마트공장 자동화산업전
Smart Factory + Automation World 2022
스마트공장 자동화산업전
글 | 박진아 기자 jin@ntrex.co.kr
아시아 최고의 스마트공장 자동화산업전인 스마트팩토리+오토메이션월드2021가 지난 9월 코엑스에서 개최됐다.
스마트공장 자동화산업전은 31년 전통을 가진 아시아를 대표하는 산업자동화 전시회로 스마트 공장의 설비나 시스템에 적용되는 기술 및 통합솔루션뿐만 아니라 스마트 센서, 빅데이터 기반 클라우드 서비스, 최신 기술이 융합된 다양한 제품들을 한자리에서 만나볼 수 있다. 또한, ‘The Future of Digital Newdeal을 주제로한 이번 전시회는 1,800부스, 500개 업체가 참가 하여 코엑스 전시홀 A,B,C,D홀 전체를 사용한 대규모로 진행되었으며, 200개의 전문 컨퍼런스를 통해 차기년도 트렌드를 확인할 수 있었다.
먼저 코엑스 A,B홀에서는 국제공장자동화전으로 자동화시스템, 산업용 로봇, 모션컨트롤러, 제어 기기등을 전시하였는데 그 중 국내 자동화 브랜드로 유명한 오토닉스에서 세이프티 제품을 시연하는 모습을 볼 수 있었다.
시연된 제품은 세이프티 라이트 커튼 시리즈인 SFL / SFLA 시리즈로 세계적으로 강화된 산업현장의 안전 규제와 관심에 따라 산업용 제어기기중에 주요한 제품으로 인식되고 있다. 여기서 라이트 커튼이란 위험원으로부터 작업자의 안전을 확보하기 위한 목적으로 펜스나 장비등에 설치해 검출 영역 내 물체 감지 시 기계 작동을 즉각 정지시키는 산업용 안전 제품을 말한다.
SFL/SFLA 시리즈는 손가락, 손, 인체를 검출할 수 있는 라인업으로 구성돼 있으며 144mm에서 1,868mm까지 다양한 높이를 제공해 자비 개구부에 맞는 최적의 길이를 제공한다.
또한, 국제 규격 및 규제에 적합한 오토닉스 세이프티 라이트 커튼은 15m 장거리 검출, 최대 400광축 확장, 리듀스드 제롤루션, 뮤팅, 블랭킹 등 안전과 관련된 다양한 기능을 제공한다.
해외 의존도가 높은 세이프티 제품이 국내 브랜드 오토닉스에서 출시됨에 따라 전시회장에서 많은 관람객들이 해당 부스를 방문하여 다양한 오토닉스의 세이프티 제품들을 살펴보았다.
계측장비 전문 유통사인 네모테크에서는 글로벌 계측장비 플루크(Fluke)의 배터리 분석기 BT500시리즈, 교정기, 전력분석기, 산업용음향카메라, 열화상 카메라등을 선보였다.
플루크 브랜드는 1984년 설립된 세계 1위의 산업계측기 브랜드로 견고함, 안전성, 편의성 부분에서 높은 명성을 지니고 있다.
이 날 선보인 729 자동 압력 교정기는 이런 명성에 맞게 휴대가 가능하며, 목표 압력을 입력하기만 하면 교정기가 자동으로 원하는 설정 지점으로 조정할 수 있고, 내부 미세 조정 컨트롤이 요청된 값에서 압력을 자동으로 안정화 시키는 기능이 있다.
이외 주요제품인 ii910 정밀 음향카메라는 모든 누출 및 PD 문제를 사전에 감지하여 대응할 수 있는 도구이다. 절연체, 변압기, 스위치 기어 또는 고전압 전력선 등 어떤것을 스캔하더라도 부분방전, 가스, 진공 누출의 위치를 찾을 수 있도록 설계되었다.
산업 현장에서 이상징후를 사전에 감지할 수 있는 도구로는 음향카메라외에 열화상 카메라도 볼 수 있는데 플루크에서는 휴대용, 접이식, 설치형 등 현장 상황에 맞는 열화상 카메라를 선택할 수 있게 지원하며 광범위한 응용분야에서 사용될 수 있다고 한다.
세계 최대 반도체 정밀 제어시스템 제조 공급사 하이원코퍼레이션에서는 리니어 스테이지, 서보모터, 액츄에이터, 단축로봇, 리니어 가이드 웨이, 볼스크류, 다축 로봇을 전시하였다.
하이원 볼스크류는 나사회전축과 순환시스템이 통합되어 있는 너트로 구성되어 있다. 볼의 회전운동을 직선운동으로 변환시키며 공작기계, IT, 광전, 반도체 의료 등 정밀 설비에 사용하여 높은 위치결정 정도와 고효율 및 작은 회전력으로 커다란 추진력을 얻을 수 있다. 또한 과학기술, 제조 기술, 공정 전문 기술의 조합체이다.
현장에서는 다축 로봇의 시연도 볼 수 있었는데 하이원 다축로봇은 제어, 소프트웨어, 핸드헬드 장치를 포함하고 있다. 6축 조인트 암 로봇은 높은 유연성과 높은 반복 정밀도로 특징지어지며 소형 제어 및 드라이브 시스템을 갖추고 있다. 또한, 조작기 사용자 마스크를 사용하면 필요한 제어 요소를 쉽고 빠르게 배치할 수 있으며 유연한 조작이 가능하다고 한다.
많은 관람객으로 인산인해를 이뤘던 또 다른 부스로는 협동로봇용 그리퍼 제품을 제공하는 온로봇 코리아를 볼 수 있었다.
온로봇의 그리퍼 제품들은 다양한 협동 로봇과 호환되어 작업자와 함께 복잡한 프로세스를 처리할 수 있다고 한다. 15종의 그리퍼, 힘/토크 센서 및 툴체인저는 조립, 자재물류, 자재절삭, 머신탠딩, 품질 시험등 모든 애플리케이션을 단시간내에 완성할 수 있게 도와준다.
그리퍼 별로 다양한 자재 및 크기의 제품과 사용이 가능하기에 광범위한 분야에서 사용자에게 선택의 다양성을 제공해주며, Doosan, TM Robot, Yaskawa, Universal Robots, KUKA, Fanuc, Kawasaki Robotics, Hanwha 및 Nachi 등 브랜드와 호환된다.
이미지와 같이 현장에서 박스를 옮기는 시연을 하고있던 그리퍼 제품은 VGP20그리퍼로 판지 상자 등의 물체를 적재하는데 이상적인 전기 진공이다. 이 제품은 업계에서 가장 강력한 전기 진공 그리퍼로 최대 20Kg의 유효 하중을 취급할 수 있으며 공압식 그리퍼의 비용, 복잡성 및 유지 보수가 전혀 필요하지 않다고 한다. 이밖에도 협소한 공간 및 고하중의 물체에 적합한 병렬 그리퍼, 와이드 스트로크가 있는 핑거 로봇 그리퍼 제품등 을 실제로 확인할 수 있었다.
B홀에서는 국제공장자동화전 외에도 한국머신비전 산업전이 마련되어 다양한 산업용 카메라, 렌즈 및 조명, 영상처리 소프트웨어 등의 머신비전 제품및 솔루션을 집중적으로 확인할 수 있었다.
먼저 의료 영상 처리, 광학 신호 처리, 이미지 센서 등 다양한 분야의 영상 기술을 연구, 개발해온 뷰웍스에서는 다양한 산업용 카메라를 전시했다.
산업용 카메라는 산업 자동화의 핵심 분야인 머신비전에서 눈을 담당하는 부품으로, 검사 대상의 품질 관리와 안정화를 위해 사용된다.
뷰웍스는 사용자가 작은 픽셀 하나의 오차도 찾아 낼 수 있는 고해상도 픽셀 시프트 기술을 적용한 초고해상도 카메라를 제공하고 있으며, 열정 냉각 기술을 적용하여 카메라 센서 온도를 주변 온도보다 최대 20도까지 낮춰 균일한 화질을 유지한다.
또한, 뷰웍스 산업용 카메라는 열전 냉각 기술이 적용된 고성능 카메라 VP 시리즈, 고속 CMOS 카메라인 VC시리즈, 고성능 및 비용 효율적인 라인 스캔 카메라 VL시리즈 등 다양한 시리즈를 제공하여 사용자의 필요에 맞는 카메라를 선택할 수 있다.
조명 및 조명 컨트톨러 제작기업인 엘브이에스에서는 일반 조명에서부터 파워서플라이, LED 광소스 관련 제품을 전시했다.
사진속 HPLS-S/FS50 제품은 광섬유 LED 광원인 LVS-HPLS 시리즈중 하나로 조명부와 컨트롤러부가 분리되어 있어 쉽고 편리하게 설치가 가능하다. 빛의 종류는 가시광선이며 광원은 광섬유로 발광색은 녹색, 적색, 파란색, 흰색을 지원한다.
S50의 경우 외경 사이즈가 32m, F50은 54m로 설치장소가 협소하거나 접근이 어려운곳에서도 장착이 용이하고, RS-485사용 시 64대까지 연동이 가능한 특징이 있다.
또한, 광화이버는 물론 전면부 렌즈 교체만으로 스폿용 조명으로도 사용이 가능하며 전면 판넬에서 시스템의 모든 내부환경을 확인할 수 있는 장치가 장착되어 있다.
공장자동화에서부터 물류 자동화, 공정 자동화에 이르기까지 다양한 자동화 솔루션을 이끄는 글로벌 브랜드 SICK에서는 보유하고 있는 다양한 자동화 센서, 스위치, 각종 기기들을 전시하였다.
워낙 다양한 제품군을 보유한 브랜드로 비전 솔루션, 광범위한 광전 센서 , 라이다 등 살펴볼 제품이 많았지만 주요제품으로 콘트라스트 센서 선두주자로 해당 센서를 빼놓을 수 없었다. 콘트라스트 센서는 주로 포장 기계와 인쇄기에서 마크 감지에 사용되는데 SICK의 콘트라스트 센서인 KT-시리즈는 고속에서 미세한 필름 또는 포장의 인쇄 마크를 감지한다.
무광택 표면, 광택 표면 또는 투명 표면에서 마크와 배경 사이 무채색 스케일의 미세한 차이를 인식한다. KT 시리즈중 KTX와 KTS는 Twin-Eye-Technology를 적용하여 피사계 심도 및 감지 범위 허용오차가 커졌으며 동작 범위가 넓어 반짝이는 재료에서 콘트라스트를 확실하게 인식하는 특징을 갖는다.
국내에서 처음으로 녹색 4각형 로봇을 선보인 민트로봇에서는 Pal A 원통형 스카라 로봇을 처음으로 공개하였다. 세련된 Pal A 원통형 스카라는 기존 스카라 로봇들과 달리 형태의 차별화가 가장 눈에 띄었다. 해당 모델은 평균적인 스카라 로봇의 수직 운동범위보다 2배 이상 긴 작업 범위를 갖고 있어 기존 스카라 로봇이 할 수 없던 업무를 대체 할 수 있다. 또한, 베이스와 몸통 링크를 이어주는 첫 번째 관절이 360도 회전을 하며 수직축은 긴 거리의 운동을 위하여 특수 제작된 볼스크류 기반의 병진 관절이 장착되었다. 엔드이펙터가 부착되는 암 관절의 크기가 스카라에 비해 확연히 작다는 장점을 갖고 있다.
스카라 계열 로봇은 좁은 공간 내에서 반복적인 업무에 최적화되어 있어 전자제품 및 반도체 조립, 식품, 농산물, 음료 산업 분야에서도 폭넓게 적용이 가능하다고 한다.
유진로봇은 Industry 4.0 및 Logistics 4.0에 맞춰 공장 자동화, 자율 모바일 로봇, 연구 목적을 위한 모바일 플랫폼 데모 키트, LiDAR 센서, SLAM 기술 및 탐색 소프트웨어와 같은 제품과 서비스를 제공한다. 유진로봇 부스의 한쪽면에는 유진라이다가 전시되어 있었는데, 2D, 3D 제품으로 총 3가지의 시리즈로 구분되었다.
먼저, YRL2 시리즈는 실내 2D 스캐닝 라이다로 270도 수평의 정보를 스캔할 수 있으며 측정범위는 5m, 10m, 20m로 구분된다. YRL3 시리즈는 3D 실내 스캐닝 라이다로 기존 2D의 270도 수평 외에도 수직 90°(-45° ~ +45°)의 정보를 스캔할 수 있다. 마지막으로 YRL3-V2제품은 측정 범위 25m에 수평 360도, 수직 55도의 시야각을 가진 라이다로 출시 예정이다. 전시된 라이다 외에도 부스 한편에서는 유진 로봇의 자율 이동로봇이 부스 이곳저곳을 돌아다니며 시연하는 모습을 볼 수 있었는데 물류 및 자재 운송과 같은 다목적 개방형 애플리케이션을 위해 설계된 고카트 제품이라고 한다.
해당 제품은 유진 라이다 기반 3D SLAM & Navigation을 특징으로 매핑 및 위치 파악, 민첩한 경로 계획, 인공 마커를 사용하지 않고 빠른 물체 인식 및 회피가 가능하다. 또한, 컴팩트한 크기로 병원, 공장, 창고와 같은 넓은 상업 및 산업 시설은 물론이고 역동적이고 좁은 환경에서도 매우 효율적이라 한다.
이번 스마트공장 자동화 산업전은 국제공장자동화전, 스마트공장엑스포, 한국머신비전산업전의 내용 집약된 대규모의 전시로 산업자동화 업계 현황뿐만 아니라 앞으로의 트렌드도 예측할 수 있는 뜻깊은 자리로 막을 내렸다.
코로나 등으로 인해 개최가 지연된 전시였던 만큼 참가기업들이 기대와 철저한 준비를 하고 나온것 같아 보였다. 또한, 대규모 전시로 많은 기업과 바이어들도 방문하여 전시회장 곳곳의 활력이 넘치기도 하였다. 이제 선택이 아닌 필수의 단계로 나아가는 산업 자동화와 관련된 기술, 솔루션들이 지속적으로 성장하기 바라며 이번 관람기를 마친다. DM
[68호] 라즈베리파이 피코용 HM0360 VGA 카메라 모듈 출시
ArduCAM
라즈베리파이 피코용 HM0360 VGA 카메라 모듈 출시
오픈소스 하드웨어 및 소프트웨어 전문 기 업이며 아두이노와 라즈베리파이 카메라 솔 루션을 제공하는 ArduCAM은 라즈베리파이 피코용 카메라 모듈인 HM0360 VGA 제품을 출시했다. ArduCAM HM0360은 라즈베리파이 피코 와 모든 RP2040 개발 기판을 위해 특별히 제작된 저가 카메라 모듈이다. 카메라 시스템 지연 및 전력 소비를 줄이기 위해 HM0360은 온칩 자체 발진기, 실시간 클록, 빠른 초기화, 컨텍스트 전환과 즉각적 인 프레임 업데이트를 제공한다. 초저전력, 고감도, 저잡음이 특징인 VGA 센 서를 탑재한 이 제품은 모니터 모드에서 최 저 240µA 까지 7.8mA VGA 60FPS를 작동 할 수 있고 센서는 프로그래밍 가능한 인터 럽트와 함께 여러 모니터링 옵션을 제공하며, 호스트 프로세서가 센서에 의해 알림을 받을 때까지 대기 상태에 놓일 수 있다. 외부 프레임 동기화 및 스테레오 카메라를 지원하고 1레인 MIPI CSI2 및 1비트, 4비트 및 8비트 프로토콜을 지원하는 8비트 병렬/ 직렬 데이터 형식이다. 빠른 레지스터 액세스를 위한 버스트 작동 을 지원하는 I2C 2- 와이어 직렬 인터페이 스와 로우프로파일 모듈 설계를 위한 높은 CRA가 이 제품의 특징이다. 상시 가동할 수 있는 애플리케이션에 적용 하기 좋은 이 제품은 디바이스마트에서 판매 중이며 해외 수입 제품으로 납기는 영업일 기 준 약 7일 정도 소요된다.
[67호]딥러닝을 결합시킨 임베디드 기구 오목 AI : 베타오
2021 ICT 융합 프로젝트 공모전 대상
딥러닝을 결합시킨 임베디드 기구 오목 AI : 베타오’
글 | 호서대학교 박정준, 김진우, 박정섭, 김선혁, 장서윤, 이성용, 이준용
1. 심사평
칩센 Vision을 활용한다고 했을 때 바둑판과 바둑돌은 매우 명확하게 구분이 가능한 환경이라 볼 수 있겠습니다. 따라서 작품의 완성도는 AI의 성능과 robot의 움직임을 포함한 성능이 될 것으로 보여졌습니다. 동영상 및 보고서상으로 확인한 바로는 프로파일을 LM 가이드로 움직임에 따라 상부에 설치된 카메라에 간섭이 자주 발생하게 되는 부분이 구조적인 아쉬운 느낌이 들었습니다. 향후 실제 경기용으로 적용을 하기 위해서는, 현재의 작품은 사람과 AI 데이터를 기반으로한 로봇간의 대결이나, 로봇간의 대결을 통한 머신/딥러닝 과정을 진행함으로써 쌓인 데이터 축적으로 난이도와 완성도를 높일 수 있을 듯합니다.
펌테크 딥러닝, AI로봇, 영상처리 등의 난이도가 있는 기술을 효율적으로 접목하여 기획의도에 맞게 시스템을 안정적이면서 완성도 높게 구현한 인상적인 작품으로 작품의 아이디어와 창의성이 돋보이며 전체적으로 기획의도, 기술 구현도, 완성도 등에서 상당히 뛰어나고 우수한 작품으로 생각됩니다.
위드로봇 영상처리, DNN 학습 및 응용, 로봇 제어가 모두 포함된 완성도 높은 작품입니다.
뉴티씨 요즘 핫이슈이고 AI를 배워서 나오게 되면 연봉도 많이 받을 수 있다고 하죠? 어딜가도 AI가 대세입니다. AI를 접목하고 CNC 등을 동작시켜 오목을 둘 수 있도록 한 아이디어가 참 좋습니다. 로봇도 학습하고 AI도 학습하고. 교육 목적으로도 참 좋은 작품 같습니다. 비슷한 기술로 생각해보면 카메라로 사진을 찍고, 그것을 영상처리하여 스케치한 것처럼 만들고, 그것을 그리는 로봇도 쉽게 만들 수 있을 것 같습니요. 수고많으셨습니다.
2. 작품 개요
제작 동기
로봇자동화공학과에 재학 중 ‘AI 인공지능과 AI 인공지능을 직접 눈으로 볼 수 있는 하드웨어를 구상하던 중 오목 AI와 오목을 둘 수 있는 하드웨어를 구상해보자’는 아이디어를 얻어 Omok AI 베타오를 제작하게 되었다.
제작 목적
알파고와 이세돌의 경기 후, 공학계에서는 인공지능의 중요도가 급부상했다. 하지만 교육적으로 임베디드에 AI를 활용한 사례는 극히 드물다. SW 교육과 AI 활용에 도움이 될 수 있는 AI 오목 게임 로봇이다.
작품 요약
Omok AI는 gomocup의 데이터를 이용하여 CNN 기반으로 최적의 다음 수를 둘 수 있는 AI이다. 이를 통해 openCV로 검은돌(사용자)가 돌을 둔 곳의 위치를 인식 받아 아두이노와 하드웨어가 3D 프린터, CNC 가공 기계와 같이 움직여 오목의 돌을 옮겨주는 로봇이다.
3. 작품 설명
작품 주요 기능
작품 주요 기능은 크게 하드웨어, 소프트웨어, 임베디드 시스템 3가지로 구성되어 있다.
하드웨어는 카메라가 고정되어 달리고 모터가 x, y, z 3차원 직교좌표계를 따라 움직이는 본체이다. 서보, 리니어, 스텝 모터 3종류를 제어해 3D 프린터와 비슷하게 일정 높이에서 돌을 이동시킨다. 이로써 플레이어가 손을 대지 않아도 컴퓨터와 게임에 참여할 수 있는 것을 확인할 수 있다.
소프트웨어는 크게 오목 경기를 화면 GUI상에서 확인하고 게임에 참여할 수 있도록 짜인 모듈의 집합체인 GUI 프로그램, 경기를 할 수 있는 Ai 학습 과정 전체에 대한 학습 코드, 파이썬과 아두이노를 통신, 아두이노를 제어하는 코드 세 가지로 나뉜다.
GUI 프로그램은 게임에 참여하는 플레이어가 오목 경기를 하는 도중 사용할 오목판 GUI의 색과 좌표, 돌의 크기 등을 속성값으로 변환할 수 있다. 또 오목의 룰을 정의해 게임의 승패 조건을 판단하고 정의한다. 이때 사용되는 오목의 룰은 Freestyle(자유룰)로 오목돌이 5개 이상 모였을 경우에 승리하는 룰을 사용한다.
학습 코드는 gomocup에서 2020년 결과 데이터를 얻어 CNN으로 학습한다. 오목 경기가 이루어진 수많은 데이터를 전처리해서, 컴퓨터-사람 간의 경기가 이루어질 수 있도록 게임의 룰을 이해하고 학습할 수 있도록 한다.
임베디드 시스템은 영상처리 과정을 거친 좌표값을 받아 하드웨어를 구성할 수 있는 제어 보드 및 프로세스이다. 모터제어를 위해 아두이노 우노를 사용해 시리얼 통신으로 PC로 좌표값을 보낸다. 메인 제어기인 아두이노 우노는 총 2개를 사용하며 각각 x축 스텝모터, y축 스텝모터와 리니어, 서보모터를 제어한다.
작품 활용 방안
카메라를 활용해 바둑판을 인식하고 원하는 좌표값을 받아 알맞은 위치에 돌을 두고, 오목 경기를 진행할 수 있도록 하는 작품이다. 실제 오목 경기를 연습할 수 있는 AI 심판을 제작하는 셈이지만, 임베디드에 AI를 적용시키는 프로젝트로써 관련 컨텐츠가 교재 및 강의와 함께 교육용 작품으로 활용할 수 있다.
3.1. 주요 동작 및 특징
3.2. 전체 시스템 구성
전체 시스템 구성도
시스템은 다음과 같이 구성되어있다. 하드웨어와 임베디드 시스템은 PC를 통해 영상 처리 결과 정보 및 좌표값을 시리얼 통신을 통해 수신받는다. 웹캠-PC간에 영상처리 정보 결과가 양방향으로 전달되며, 파이썬 IDE 파이참이 시리얼 통신을 통해 아두이노에 좌표값을 전달한다. 사람과 AI가 플레이를 하는 동안 파이썬은 지속해서 게임의 룰을 지키며 다음 돌의 최적의 위치를 예측하여 두게 된다. 최종적으로 위의 세 구성 요소가 상호작용함에 따라 시스템이 동작한다.
세부 구성도
세부 구성도는 [그림 4]와 같이 크게 HW, Embedded 시스템, PC/CAM, SW로 구성되어 있다.
하드웨어 구성도
소프트웨어
3.3. 개발 환경
소프트웨어
파이썬 IDE 파이참, 학습을 위한 jupyter notebook
모터 제어를 위한 Arduino IDE
개발 환경
Python 3.7.10 환경과 jupyter 가상환경 구축 및 실행
Tensorflow 2.4.0 version & GPU RTX 2060 SUPER
영상 인식(openCV)
아두이노와 파이참 통신
임베디드 시스템
임베디드 시스템 제작 시 준비물
하드웨어
사용 시스템 및 Tool
솔리드웍스, DP103, 3DWOX, 레이저 커터, 3D 펜 등을 가지고 작품을 제작하였으며 각각의 세부 설명은 다음과 같다. 솔리드웍스는 3D CAD 설계 Tool로 스텝 모터 고정 대, LM 가이드라인, 바둑돌 사출기, 스위치 고정 등에 사용되었으며 설계 파일 및 도면은 최종 원고에 사용한 자료에 포함되어 있다. 솔리드웍스의 버전은 Solidworks 2019 버전을 사용하였다.
3D 프린터는 Sindoh 사의 DP103을 사용하여 프린팅하였으며 stl 파일을 Gcode로 변환해주는 Tool은 3DWOX를 사용하였다. 또한 3D 펜을 사용하여 3D 프린터 출력물을 수정 보완하였으며 스위치 고정 지지대 출력 및 프로파일 고정대 출력 등에 사용하였다.
HW 제작 시 준비물
4. 단계별 제작 과정
하드웨어(컨셉 설계도 및 설계도)
초기 컨셉 설계도로 x, y축의 초기 설정과 베타오의 기본 베이스가 되는 설계도이다.
베타오의 최종 완성된 사진이다. 최종 완성되기까지의 과정은 아래와 같은 과정을 거처 진행된다.
사진과 같이 오목판의 크기를 측정하여 측정된 크기에 맞게 프로파일을 가공하여 연결한다. 모든 단계의 기초이자 가장 중요한 부분이므로 프로파일과 프로파일을 연결하는 알루미늄 다이캐스트 브라켓과 렌치볼트를 사용하여 단단하게 고정해야 한다.
y축이 x축 위에서 움직일 수 있도록 해주는 LM 가이드 라인을 프로파일과 연결할 수 있도록 설계한 것이다. LM 가이드의 수평과 고정을 동시에 고려하며 설계한 설계 부품이다. 스테인레스 재질의 LM 가이드 라인DMF 사용하려 계획했었지만, 프로파일과의 정교한 고정과 비용적인 측면에서 어려움을 겪어 기존에 있는 스테인레스 재질의 LM 가이드 라인을 역설계하여 베타오에 맞게끔 설계하였다.
[그림 47]와 같이 프로파일 지지대의 상층부를 프로파일과 알루미늄 다이캐스트 브라켓으로 연결한 후 수평을 맞추어준다. 이 과정에서 수평은 수평계를 활용하여 맞춘다. LM 가이드 라인과 LM 가이드가 최소한의 마찰을 가지며 움직여야 정확한 좌표를 얻을 수 있기 때문에 이 과정에 시간을 많이 투자해야 된다. 이 과정을 거치고 z축에 바둑돌 사출기를 장착하면 [그림 48]와 같아진다.
스위치는 스텝 모터의 x, y축의 양단에 한계점의 신호를 받기 위해 2개를 장착한다. 각각의 스위치를 고정하는데 3D 펜을 사용하여 보다 정밀하게 제작하였다. 스위치의 위치가 바로 원점의 위치가 되므로 정밀하게 제작되어야 한다. [그림 50]와 같이 카메라를 고정하였는데 이는 openCV에서 바둑판의 전체를 인식 받아야 하기 때문에 중앙에 설치하게 되었다.
[그림 51]와 [그림 52]의 ‘.SLDPRT파일’은 솔리드웍스를 활용한 y축, z축의 설계 부품이며 stl은 3D프린팅을 위한 Gcode 변환하기 전의 파일로 3DWOX Tool을 활용하여 Gcode로 변환하여 DP103 프린터를 이용하여 프린팅이 가능하다.임베디드 시스템
아두이노 통신
리니어 모터와 서보 모터를 활용하여 z축의 바둑돌 사출기를 만들기 전 테스트를 위해 두 개의 모터만 따로 제어한 것이 [그림 61]이다. 리니어 모터를 통해 z축의 높이를 조절하며 서보 모터를 통해 바둑돌을 하나씩 바둑판으로 사출시킨다.
소프트웨어
알고리즘 및 코드 진행 과정
알고리즘 및 코드 진행 과정을 간략화한 순서도이다. 각 과정을 통해 학습된 결과와 그 학습된 결과를 활용한 오목 GUI와 openCV 영상처리를 통한 좌표값이 서로 상호작용하며 아두이노 제어 보드에게 좌표값을 정제하여 전달한다.
파이참 환경 및 외장 함수 import
numpy, glob, tqdm, os, ursina, tensorflow, pythonopenCV, serial 파이썬 외장 함수를 다운로드한다. 다운로드된 외장 함수를 이용하여 함수를 불러와 필요함수를 사용한다.
데이터 전처리 과정(데이터셋 생성 과정)
create_dataset.py를 실행하여 gomocup2020results 폴더에 저장 되어 있는 ‘.psq’ 파일의 데이터를 바둑판의 크기와 돌의 위치를 받아온다. 전처리하기 전 데이터를 위와 같은 ‘.psg’ 파일로 저장되어 있는데 Fastgame, Freestyle, Renju, Standard와 같이 게임에 적용되는 룰이 다양하다. 그 중 Freestyle과 같은 게임 룰을 가지고 데이터셋을 만든다. ‘.psq’ 파일 중 학습에 필요한 데이터만을 ‘.npz’파일로 저장한다.
openCV(영상 인식)
먼저 바둑알 사진을 불러온다. 불러온 사진을 이진화하여 노이즈를 제거한다. 다음으로 실시간 영상을 불러온다. 실시간 영상 또한 이진화 처리하여 노이즈를 최대한 줄인다. 그 후 검은색을 인식하기 위해 픽셀의 값이 임계값(threshold) 이하이면 하얀색으로, 일정값 이상이면 검은색으로 픽셀의 값을 바꾼다. 이 과정을 통해 검은 바둑돌만 검정색으로, 다른 배경은 흰색으로 칠한다. 그 이후 처음 불러왔던 바둑돌과 비교하여 임계값(threshold) 이상의 정확도를 가질 경우 그 좌표를 불러온 사진의 크기만큼 초록색 사각형(컨투어)을 칠한다. 또한 사각형의 중심에 파란색 점을 찍는다. 그 점 값을 전처리를 통해 기존 검은돌의 좌표를 저장한 배열에 존재하지 않을면 값을 추가한다. 또한 새롭게 추가된 좌표값만 출력한다.
[그림 75]과 같이 검은돌을 바둑판에서 인식받아 정해진 임계값에 따라 컨투어를 생성하고 좌표를 생성한다.
좌표값을 인식해서 gui상에서 돌을 둠
5. 작품 결과
6. 피드백 및 발전 방향
6.1. 피드백
베타오의 특징점은 PC가 사람과 직접 오목 게임을 플레이까지 할 수 있다는 점이다. 하지만 실제 경기에 활용할 수 있도록 하기 위해서 다음과 같은 부분이 해결되어야 한다.
난이도 조절이 필요하다.
학습 결과가 높을수록 컴퓨터가 이길 확률이 높기 때문에 정확도를 조절할 수 있는 학습 모델을 따로 생성해야 한다. .h5파일을 활용해 좀 더 낮은 수준의 인공지능을 적용할 수 있도록 수정할 예정이며, 경기에 적용했을 때의 난이도까지 테스트할 것이다.
33 규칙을 적용해야 한다.
실제 오목 경기에서는 한 번에 두 줄이 3, 3이 되는 경우는 이겼다고 규정하지 않는다. 승패가 무효가 되는 중요한 규칙이므로 이 점에 대해 보완할 것이다.
흑돌이 유리하다
오목은 확률론적으로 흑돌이 무조건적 우위를 선점한다. 현재 흑돌은 사람을 기준으로 선언되어있기 때문에 유저의 기호에 맞춰 턴을 바꿀 수 있도록 사전 UI를 구성하여 선택할 수 있도록 수정할 것이다.
6.2. 발전방향
1) 일반인이 직접 활용할 수 있는 딥러닝 교육용 제품으로 사용할 수 있다. 영상처리, 시리얼 통신, 임베디드 시스템, 딥러닝을 결합한 교육용 SW로써 각 분야를 하나의 시스템으로 구성하는 경험을 할 수 있다.
2) 자율주행 및 자동화 공정과 같은 실제 시스템에 응용해서 사용할 수 있다. 영상처리 기반으로 좌표 값을 추출하여 스텝 모터를 기반으로 정밀하게 좌표값 위치로 이동시킨다. 이는 정밀공정과 같은 다양한 시스템에 적용이 가능하다.
3) 오목 경기 연습 AI 및 학습 연구 대상이 될 수 있다. 또한 현재 개발된 모델과 매우 흡사한 바둑과 체스 등 다양한 시스템에 적용할 수 있다. 알파고의 출범 이후, 프로 바둑 기사들은 인공지능의 수와 흡사하게 두는 방법으로 실력을 키우고 있다. 이와 비슷한 맥락으로 본 프로젝트로 개발된 오목 AI도 오목 프로 기사를 양성하는 과정에서 사용될 수 있다.
4) 이벤트성 행사 및 공공일정에서 사용할 수 있다. 베타오는 소프트웨어가 하나의 압축 디렉토리로 송수신이 가능하기 때문에 하드웨어만 준비될 수 있다면 충분히 전시회 및 박람회에서 사용할 수 있는 행사 기구로 발전할 수 있다.
기술적 측면에서의 발전 방향
본 프로젝트는 기술적으로 크게 MUC 기반의 임베디드 제어 시스템, 바둑알을 인식하기 위한 영상처리, 오목 알고리즘 구현을 위한 CNN 기반 AI 등의 SW 부분과 리드 스크류, LM가이드를 이용한 오목 로봇의 좌표 이동, 리니어 모터를 이용한 바둑알 사출 시스템 등의 HW 부분으로 구분할 수 있으며 각 분야별 발전 방향은 다음과 같다.
소프트웨어
영상 인식
고사양 GPU를 사용해 처리 속도를 향상시킨다. 이는 영상 인식과 모델 생성 속도에도 영향을 주며 GPU의 성능에 따라 증가한다. 또한 사용하고 있는 openCV 정적라이브러리(static library)를 동적 라이브러리의 사용으로 변환하면 최소한의 프로그램 사용으로 같은 수준의 영상 인식을 사용할 수 있어 간단해진다.
오목 AI
생성된 모델을 모델로 학습하는 GAN(Generative Adversarial Network)을 사용한다. GAN을 사용하게 되면 생성 모델의 성능을 압도적으로 향상시킬 수 있다.
임베디드 시스템
컴퓨터와 메인 제어 보드를 함께 사용할 수 있도록 아두이노 우노를 라즈베리파이로 변경한다. 라즈베리파이 환경에 SW와 임베디드 시스템을 합쳐 베타오의 크기를 줄일 수 있다.
하드웨어
교육기관 도입 및 대량생산을 목표로 할 시, 하드웨어의 장치적 단순화 및 경량화가 가능해진다면 좀 더 유동적이고 용이한 작품으로 발전할 수 있다.
7. 사용한 제품 리스트
8. 참고문헌
· 미니츄어 LM가이드 표준 블록/경예압.”미니츄어 모델의 정확한 치수 “.https://kr.misumi-ec.com/vona2/detail/110302586530/?KWSearch=lm%ea%b0%80%ec%9d%b4%eb%93%9c&searchFlow=results2products.(2021.03.13)
· 라즈이노 iOT.”[ 아두이노 기초 ] #31 스텝(Step Motor)모터의 이해.”스텝모터의 원리”.https://rasino.tistory.com/149.(2021.03.17)
· a4988 스텝 모터 드라이버 알아보기 ( 스테퍼 / 스테핑 / 모터 / 드라이버 / 초퍼 모터드라이버 / 설정 ).”스텝모터의 정격전압과 회로 구성 방법”.https://m.blog.naver.com/PostView.nhn?blogId=roboholic84&logNo=221142584042&proxyReferer=https:%2F%2Fwww.google.com%2F.(2021.03.18)
· 아두이노로 NEMA17 스텝모터 제어하기, 스텝수 계산 (A4988 스텝모터드라이버).”스텝모터의 분주 제어방법”.https://blog.naver.com/chrkwlq1208/221562820510.(2021.03.25)
· 오목을 두는 인공지능을 만들어 겨뤄봤습니다.오목인공지능 구현 방법.https://www.youtube.com/watch?v=xigPAOl3v7I&ab_channel=%EB%B9%B5%ED%98%95%EC%9D%98%EA%B0%9C%EB%B0%9C%EB%8F%84%EC%83%81%EA%B5%AD(21.03.10)
· GOMOCUP.”전처리데이터 다운로드”.https://gomocup.org/.(21.03.12)
· [아두이노 -> 파이썬]시리얼 통신으로 문자열 보내는 방법].”파이썬에서 아두이노로 시리얼 통신하는 방법”.https://doomed-lab.tistory.com/4.(21.03.20)
· 정성환, 배종욱(2005).OpenCV-Python으로 배우는 영상 처리 및 응용.생능출판사
9. 소스코드
아두이노 하드웨어 X축 방향 구동 코드
/* x축 stepping motor 구동 code */
String sig;
char Ard1[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언
char Ard2[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언
char check[1];
int value_1 = 0; // x축 좌표값 받을 변수 선언
int value_2 = 0; // y축 좌표값 받을 변수 선언
int flag = 0; // 통신 데이터의 무한한 공급을 막기 위한 변수 선언
/* stepping motor pin설정 */
#define steps_x 5
#define dir_x 6
#define ms1_x 8
#define ms2_x 9
#define ms3_x 10
/* switch pin설정 */
#define sw_x 4
long long int i = 0; // for문에 사용할 변수
//long long int i_num = 5000+4000*value_1;
long long int delay_num = (6000+4000*value_2)*0.4;
int sn = 0; // stepping motor가 회전하는 바퀴수를 count하기 위한 변수
void setup()
{
// put your setup code here, to run once:
Serial.begin(9600);
// 신호보낼 핀 출력설정
pinMode(steps_x, OUTPUT); // stepping motor에 신호를 주기 위함
pinMode(dir_x, OUTPUT); // stepping motor 방향설정을 출력
pinMode(ms1_x, OUTPUT); // stepping motor 분주 설정 출력
pinMode(ms2_x, OUTPUT); // stepping motor 분주 설정 출력
pinMode(ms3_x, OUTPUT); // stepping motor 분주 설정 출력
/* 분주를 1/2로 설정했으므로 1 step당 0.9도씩 회전 */
digitalWrite(steps_x,LOW);
digitalWrite(dir_x, HIGH); // 초기 설정 : stepping motor 정방향 회전
digitalWrite(ms1_x, HIGH); // stepping motor 1/2분주 설정
digitalWrite(ms2_x, LOW); // 분주 비활성화
digitalWrite(ms3_x, LOW); // 분주 비활성화
pinMode(sw_x, INPUT_PULLUP); // pull_up저항으로 출력 설정
/* x축의 stepping motor회전으로 초기 좌표까지 이동 */
digitalWrite(dir_x, HIGH); // stepping motor 정회전
while(1)
{
if(digitalRead(sw_x) == HIGH)
{
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지
}
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_x, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_x, LOW);
delayMicroseconds(200); // delay 2us
}
kn_s:
delay(200);
}
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(dir_x, HIGH); // stepping motor 정회전
/* x축의 stepping motor회전으로 초기 좌표까지 이동 */
while(1)
{
//x좌표 switch가 인식될 경우에 초기 좌표로 인식
if(digitalRead(sw_x) == HIGH)
{
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지
}
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_x, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_x, LOW);
delayMicroseconds(200); // delay 2us
}
kn_s:
delay(200);
kn_x:
/* 문자열로 저장*/
while(Serial.available())
{
char wait = Serial.read(); // python serial 통신으로 받을 문자열
sig.concat(wait); // python으로 통신받은 문자열을 sig라는 빈 문자열에 추가
}
/* 입력 문자열 슬라이싱 */
sig.substring(0,1).toCharArray(check,2);
if(check[0] == ‘Q’)
{
if (sig.length()==5)
{
sig.substring(1,3).toCharArray(Ard1,3); // x축에 해당하는 좌표 문자열
sig.substring(3,5).toCharArray(Ard2,3); // y축에 해당하는 좌표 문자열
value_1 = atoi(Ard1); // x축 좌표값
value_2 = atoi(Ard2); // y축 좌표값
sig = “”; // x,y 축의 좌표데이터를 연산처리 할 수 있게 int형으로 처리한 후 문자열을 다시 받기 위한 초기화
flag = 0;
}
else if(sig.length()>5)
{sig = “”;} // 문자열을 다시 받기 위한 초기화
}
else if(check[0] != ‘Q’)
{
sig = “”; // 문자열을 다시 받기 위한 초기화
}
if(value_1 > 0) // x축 좌표가 0이상일 때 동작하기 위한 조건문
{
if(flag == 0)
{
flag = 1; // 통신 데이터의 무한 공급으로 인한 무한루프의 시스템 처리를 막기 위해 초기화
digitalWrite(dir_x, LOW);
delay(500);
/* x축 초기 좌표 위치 설정 */
for(i=0; i<6500; i++)
{
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_x, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_x, LOW);
delayMicroseconds(200); // delay 2us
}
sn = 0; // count 변수 초기화
/* y축 좌표값까지 한 바퀴씩 회전하는 값을 카운트하여 조건을 만족하면 회전 정지 및 while문 탈출 */
while(1)
{
if(sn == value_1)
{
goto ksn; // count변수가 x축 좌표값이랑 같아지면 stepping motor 회전 중지
}
for(i=0; i<4000; i++)
{
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_x, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_x, LOW);
delayMicroseconds(200); // delay 2us
}
sn++; // count 1씩 증가
}
ksn:
delay(delay_num); // y축 stepping motor 회전 시간
delay(13000); // servo & linear 작동 시간동안 delay
//회전방향 출력 10000+2000*
digitalWrite(dir_x, HIGH);
delay(500);
for(i=0; i<6500; i++)
{
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_x, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_x, LOW);
delayMicroseconds(200); // delay 2us
}
while(1)
{
for(i=0; i<4000; i++)
{
if(digitalRead(sw_x) == HIGH)
{
goto kn_x;
}
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_x, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_x, LOW);
delayMicroseconds(200); // delay 2us
}
}
}
}
}
/* y축 stepping motor & servo motor & linear motor 구동 code */
#include <Servo.h>
Servo myservo_servo; // servo motor사용을 위한 모터이름 지정
Servo myservo_linear; // linear motor사용을 위한 모터이름 지정
String sig; // 통신받을 문자열을 추가해주기 위한 자리 변수 선언
char Ard1[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언
char Ard2[3] = {0}; // 통신 받은 문자열 분할 처리를 위한 변수 선언
char check[1];
int value_1 = 0; // x축 좌표값 받을 변수 선언
int value_2 = 0; // y축 좌표값 받을 변수 선언
int flag = 0; // 통신 데이터의 무한한 공급을 막기 위한 변수 선언
/* stepping motor pin설정 */
#define steps_y 5
#define dir_y 6
#define ms1_y 8
#define ms2_y 9
#define ms3_y 10
/* switch pin설정 */
#define sw_y 4
long long int j = 0; // for문에 사용할 변수
int angle_servo; // servo motor 각도변수
int angle_linear; // linear motor 출력변수
long long int delay_num = (6500+4000*value_1)*0.4; //x축의 stepping motor회전하는 동안 동작지연하기 위한 delay
int sn = 0; // stepping motor가 회전하는 바퀴수를 count하기 위한 변수
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
// 신호보낼 핀 출력설정
pinMode(steps_y, OUTPUT); // stepping motor에 신호를 주기 위함
pinMode(dir_y, OUTPUT); // stepping motor 방향설정을 출력
pinMode(ms1_y, OUTPUT); // stepping motor 분주 설정 출력
pinMode(ms2_y, OUTPUT); // stepping motor 분주 설정 출력
pinMode(ms3_y, OUTPUT); // stepping motor 분주 설정 출력
/* 분주를 1/2로 설정했으므로 1 step당 0.9도씩 회전 */
digitalWrite(steps_y,LOW);
digitalWrite(dir_y, HIGH); // 초기 설정 : stepping motor 정방향 회전
digitalWrite(ms1_y, HIGH); // stepping motor 1/2분주 설정
digitalWrite(ms2_y, LOW); // 분주 비활성화
digitalWrite(ms3_y, LOW); // 분주 비활성화
myservo_servo.attach(11); // servo motor PIN설정
myservo_servo.write(0); // servo motor 초기 각도 0도로 설정
myservo_linear.attach(12); // linear motor PIN설정
myservo_linear.write(0); // linear motor 초기 각도 0도로 설정
pinMode(sw_y, INPUT_PULLUP); // pull_up저항으로 출력 설정
/* y축의 stepping motor회전으로 초기 좌표까지 이동 */
digitalWrite(dir_y, HIGH); // stepping motor 정방향 회전
while(1)
{
if(digitalRead(sw_y) == HIGH)
{
goto kn_s; // switch인식할 경우 kn_s:로 이동 및 stepping motor 정지
}
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_y, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_y, LOW);
delayMicroseconds(200); // delay 2us
}
kn_s:
delay(200);
}
void loop()
{
// put your main code here, to run repeatedly:
digitalWrite(dir_y, HIGH); // stepping motor 정회전
/* y축의 stepping motor회전으로 초기 좌표까지 이동 */
while(1)
{
if(digitalRead(sw_y) == HIGH)
{
goto kn_s;
}
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_y, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_y, LOW);
delayMicroseconds(200); // delay 2us
}
kn_s:
delay(200);
kn_y:
/* 문자열로 저장*/
while(Serial.available())
{
char wait = Serial.read(); // python serial 통신으로 받을 문자열
sig.concat(wait); // python으로 통신받은 문자열을 sig라는 빈 문자열에 추가
}
/* 입력 문자열 슬라이싱 */
sig.substring(0,1).toCharArray(check,2);
if(check[0] == ‘Q’)
{
if (sig.length()==5)
{
sig.substring(1,3).toCharArray(Ard1,3); // x축에 해당하는 좌표 문자열
sig.substring(3,5).toCharArray(Ard2,3); // y축에 해당하는 좌표 문자열
value_1 = atoi(Ard1); // x축 좌표값
value_2 = atoi(Ard2); // y축 좌표값
sig = “”; // x,y 축의 좌표데이터를 연산처리 할 수 있게 int형으로 처리한 후 문자열을 다시 받기 위한 초기화
flag = 0;
}
else if(sig.length()>5)
{sig = “”;} // 문자열을 다시 받기 위한 초기화
}
else if(check[0] != ‘Q’)
{
sig = “”; // 문자열을 다시 받기 위한 초기화
}
if(value_2 > 0) // y축 좌표가 0이상일 때 동작하기 위한 조건문
{
if(flag == 0)
{
flag = 1; // 통신 데이터의 무한 공급으로 인한 무한루프의 시스템 처리를 막기 위해 초기화
digitalWrite(dir_y, LOW);
delay(500);
delay(delay_num);
/* y축 초기 좌표 위치 설정 */
for(j=0; j<6000; j++)
{
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_y, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_y, LOW);
delayMicroseconds(200); // delay 2us
}
sn = 0; // count 변수 초기화
/* y축 좌표값까지 한 바퀴씩 회전하는 값을 카운트하여 조건을 만족하면 회전 정지 및 while문 탈출 */
while(1)
{
if(sn == value_2)
{
goto ksn; // count변수가 y축 좌표값이랑 같아지면 stepping motor 회전 중지
}
for(j=0; j<4000; j++)
{
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_y, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_y, LOW);
delayMicroseconds(200); // delay 2us
}
sn++; // count 1씩 증가
}
ksn:
delay(500);
/* 바둑돌이 정확한 위치로 낙하하기 위해 linear motor로 조준해주기 */
for(angle_linear = 0; angle_linear < 140; angle_linear++)
{
myservo_linear.write(angle_linear);
delay(20); // delay 10ms
}
/* 바둑돌 내려주기 위한 servo motor제어 0~180~0도 순으로 회전 */
delay(500);
for(angle_servo = 0; angle_servo <= 180; angle_servo++)
{
myservo_servo.write(angle_servo);
delay(10); // delay 10ms
}
delay(500);
for(angle_servo = 180; angle_servo >= 0; angle_servo–)
{
myservo_servo.write(angle_servo);
delay(10); // delay 10ms
}
delay(500);
/* 조준된 linear motor가 축이 움직일 때 다른 바둑돌을 건들지 않게 초기위치로 되돌림 */
for(angle_linear = 140; angle_linear > 0; angle_linear–)
{
myservo_linear.write(angle_linear);
delay(20); // delay 20ms
}
delay(delay_num);
digitalWrite(dir_y, HIGH); // stepping motor 정회전
delay(500);
for(j=0; j<6000; j++)
{
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_y, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_y, LOW);
delayMicroseconds(200); // delay 2us
}
while(1)
{
for(j=0; j<4000; j++)
{
if(digitalRead(sw_y) == HIGH)
{
goto kn_y; // y축의 switch가 눌리면 while문을 탈출하면서 stepping motor의 작동을 멈춤
}
/* stepping motor 1 step당 4us로 회전 */
digitalWrite(steps_y, HIGH);
delayMicroseconds(200); // delay 2us
digitalWrite(steps_y, LOW);
delayMicroseconds(200); // delay 2us
}
}
}
}
}
import numpy as np, cv2
import time
image = cv2.imread(‘images/go2.jpg’, cv2.IMREAD_COLOR)
capture = cv2.VideoCapture(0) #카메라 불러오는 함수
if capture.isOpened() == False: raise Exception(“카메라 연결 안됨”)
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~
ret, image = capture.read() # image로 받아옴
mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함
blue_threshold = 70 # 흑백으로 바꾸는 기준 1
green_threshold = 70 # 흑백으로 바꾸는 기준 2
red_threshold = 70 # 흑백으로 바꾸는 기준 3
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌
for i in range(image.shape[0]):
for j in range(image.shape[1]):
if (image[i, j, 0] >= blue_threshold) and (image[i, j, 1] >= green_threshold) and image[i, j, 2] >= red_threshold:
mark_1[i, j ] = 255,255,255 # 검정
else :
mark_1[i, j ] = 0,0,0 # 하얀색
ju = cv2.imread(‘images/go2.jpg’, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문
template = white_s # template = 비교할 것
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이
res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함
threshold = 0.6 # 이 값보다 높을 때
loc = np.where(res > threshold) # 위치를 리턴함
for pt in zip(*loc[::-1]): # 위치의 가로 세로
if (image[pt[1], pt[0],2] != 255):
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0)) # 중심에 파란 원
s_x = int(pt[0] + w * 0.5) # 중심 x
s_y = int(pt[1] + h * 0.5) # 중심 y
s_xy = str(s_x) + str(s_y) # str로 합침
s_xy = int(s_xy) # 다시 숫자로 바꿈
s_xy_dt = []
stone_dt = []
for i in range(0, 10): # 범위 내 값을 걸러줌
for j in range(0, 10):
s_xy_dt.append(s_xy – i*1000 – j)
s_xy_dt.append(s_xy – i*1000 + j)
s_xy_dt.append(s_xy + i*1000 – j)
s_xy_dt.append(s_xy + i*1000 + j)
xx = 0
for k in range(len(s_xy_dt)): ## 값이 같지 않으면 stack
if stone != s_xy_dt[k]:
xx += 1
yy = 0
if xx == len(s_xy_dt) : # stack 값이 다 같을 때 = 오차의 범위 값이 list에 없을 때
if s_xy < 99999 : # y축이 두자리 프레임일 때
for k in range(len(stone)):
if abs(stone[k] – s_xy) < 110: # 100의 자리 = x축 프레임 차이, 1의 자리 = y축 프레임
yy += 1
if yy == 0 :
stone.append(s_xy)
else : # x ,y 둘다 3자리 프레임일 때
for k in range(len(stone)):
if abs(stone[k] – s_xy) < 1010: # 1000의 자리 = x축, 1의자리 = y축의 차이
yy += 1
if yy == 0 :
stone.append(s_xy)
aa = len(final_stone)
for i in range(len(stone)):
stone_xx_1 = 0
stone_yy_1 = 0
if stone[i] < 99999 :
stone_x = stone[i] / 100 # 앞자리 3개
stone_y = stone[i] % 100 # 뒷자리 2개
stone_y = abs(stone_y) / 20 # 경험적 치수
stone_x = (abs((stone_x – 160) / 20) ) # 경험적 치수
else :
stone_x = stone[i] / 1000 # 앞자리 3개
stone_y = stone[i] % 1000 # 뒷자리 3개
stone_x = (abs((stone_x – 160) / 20)) # 경험적 치수
stone_y = (stone_y / 20 ) # 경험적 치수
ab = int(stone_x) *100 + int(stone_y)
if ab not in final_stone: # list에 없으면
final_stone.append(ab)
if aa != len(final_stone) : # 전의 길이와 다르면 = 새롭게 추가되면
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))
print(‘——————’)
cv2.imshow(‘mm’, image) # 이미지 출력
cv2.imshow(‘aa’, ju) # 비교 이미지 출력
cv2.waitKey(150) # delay
5neck.py
import numpy as np, cv2
import time
image = cv2.imread(‘images/go2.jpg’, cv2.IMREAD_COLOR)
capture = cv2.VideoCapture(0) #카메라 불러오는 함수
if capture.isOpened() == False: raise Exception(“카메라 연결 안됨”)
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~
ret, image = capture.read() # image로 받아옴
mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함
blue_threshold = 70 # 흑백으로 바꾸는 기준 1
green_threshold = 70 # 흑백으로 바꾸는 기준 2
red_threshold = 70 # 흑백으로 바꾸는 기준 3
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌
for i in range(image.shape[0]):
for j in range(image.shape[1]):
if (image[i, j, 0] >= blue_threshold) and (image[i, j, 1] >= green_threshold) and image[i, j, 2] >= red_threshold:
mark_1[i, j ] = 255,255,255
else :
mark_1[i, j ] = 0,0,0
#print(mark.shape)
ju = cv2.imread(‘images/go2.jpg’, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문
template = white_s # template = 비교할 것
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이
#stone = []
res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함
threshold = 0.6 # 이 값보다 높을 때
loc = np.where(res > threshold) # 위치를 리턴함
for pt in zip(*loc[::-1]): # 위치의 가로 세로
if (image[pt[1], pt[0],2] != 255):
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0))
#print(int(pt[0]+w*0.5), int(pt[1]+h*0.5))
s_x = int(pt[0] + w * 0.5)
s_y = int(pt[1] + h * 0.5)
s_xy = str(s_x) + str(s_y)
s_xy = int(s_xy)
s_xy_dt = []
stone_dt = []
for i in range(0, 10):
for j in range(0, 10):
s_xy_dt.append(s_xy – i*1000 – j)
s_xy_dt.append(s_xy – i*1000 + j)
s_xy_dt.append(s_xy + i*1000 – j)
s_xy_dt.append(s_xy + i*1000 + j)
xx = 0
for k in range(len(s_xy_dt)):
if stone != s_xy_dt[k]:
xx += 1
yy = 0
if xx == len(s_xy_dt) :
if s_xy < 99999 :
for k in range(len(stone)):
if abs(stone[k] – s_xy) < 110:
yy += 1
if yy == 0 :
stone.append(s_xy)
else :
for k in range(len(stone)):
if abs(stone[k] – s_xy) < 1010:
yy += 1
if yy == 0 :
stone.append(s_xy)
#print(stone)
aa = len(final_stone)
for i in range(len(stone)):
stone_xx_1 = 0
stone_yy_1 = 0
if stone[i] < 99999 :
stone_x = stone[i] / 100
stone_y = stone[i] % 100
#print(stone_x)
#print(stone_y)
stone_y = abs(stone_y) / 20
stone_x = (abs((stone_x – 160) / 20) )
else :
stone_x = stone[i] / 1000
stone_y = stone[i] % 1000
#print(stone_x)
#print(stone_y)
stone_x = (abs((stone_x – 160) / 20))
stone_y = (stone_y / 20 )
ab = int(stone_x) *100 + int(stone_y)
if ab not in final_stone:
final_stone.append(ab)
#for k in range(len(final_stone)):
# print(int(final_stone[k] / 100) , int(final_stone[k] % 100))
if aa != len(final_stone) :
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))
print(‘——————’)
cv2.imshow(‘mm’, image)
cv2.imshow(‘aa’, ju)
cv2.waitKey(150)
데이터 전처리 코드
import numpy as np
from glob import glob
from tqdm import tqdm # for 문의 상태바를 나태내주는 라이브러리
import os
”’
Dataset from https://gomocup.org/results/
데이터셋을 얻는 프로그램
# 데이터 전처리 과정
game_rule = ‘Freestyle’ # 프리스타일(3×3가능 5개 이상만 돌을 연속으로 두면 승리)
base_path = ‘/omok_AI/gomocup2020results’ # 전처리전 데이터가 저장되어 있는 경로
output_path = os.path.join(‘dataset’, os.path.basename(base_path)) # dataset\gomocup2019results\
os.makedirs(output_path, exist_ok=True) # 디렉토리 생성
# 경로에 있는 모든 psq파일을 file_list에 저장
file_list = glob(os.path.join(base_path, ‘%s*/*.psq’ % (game_rule, )))
for index, file_path in enumerate(tqdm(file_list)):
with open(file_path, ‘r’) as f:
lines = f.read().splitlines()
w, h = lines[0].split(‘ ‘)[1].strip(‘,’).split(‘x’) # 가로 X 세로의 크기 추출
w, h = int(w), int(h) # 문자에서 인트형으로 형 변환
lines = lines[1:] # 첫번째 줄을 제외한 나머지 줄
inputs, outputs = [], [] board = np.zeros([h, w], dtype=np.int8)
for i, line in enumerate(lines):
if ‘,’ not in line:
break
x, y, t = np.array(line.split(‘,’), np.int8)
if i % 2 == 0: # 오목의 순서제
player = 1
else:
player = 2
input = board.copy().astype(np.int8)
input[(input != player) & (input != 0)] = -1
input[(input == player) & (input != 0)] = 1
output = np.zeros([h, w], dtype=np.int8)
output[y-1, x-1] = 1 # h w 순서
# augmentation
# rotate 4 x flip 3 = 12
# 데이터셋을 늘림
for k in range(4):
input_rot = np.rot90(input, k=k)
output_rot = np.rot90(output, k=k)
inputs.append(input_rot)
outputs.append(output_rot)
inputs.append(np.fliplr(input_rot))
outputs.append(np.fliplr(output_rot))
inputs.append(np.flipud(input_rot))
outputs.append(np.flipud(output_rot))
# update board
board[y-1, x-1] = player
# dataset 저장
np.savez_compressed(os.path.join(output_path, ‘%s.npz’ % (str(index).zfill(5))), inputs=inputs, outputs=outputs)
Execute_GUI.py
from ursina import *
import numpy as np
from tensorflow.keras.models import load_model
from omok import Board, Omok
import serial
import time
# from serial_arduino import coordinate_str
PORT = ‘COM13′
BaudRate = 9600
sendSerial = serial.Serial(‘COM13′, BaudRate) # x축
ardSerial = serial.Serial(‘COM11′, 9600) # y축
# sendSerial = serial.Serial(‘COM14′, BaudRate)
# 오목 6개 만들시에도 게임을 승리하는 부분 보완
model = load_model(‘models/20210307_232530.h5′)
app = Ursina()
window.borderless = False
window.color = color._50
w, h = 20, 20
camera.orthographic = True
camera.fov = 23
camera.position = (w//2, h//2)
board = Board(w=w, h=h)
board_buttons = [[None for x in range(w)] for y in range(h)]
game = Omok(board=board)
Entity(model=Grid(w+1, h+1), scale=w+1, color=color.blue, x=w//2-0.5, y=h//2-0.5, z=0.1)
for y in range(h):
for x in range(w):
b = Button(parent=scene, position=(x, y), color=color.clear, model=’circle’, scale=0.9) # 마우스 왼쪽 클릭
board_buttons[y][x] = b
def on_mouse_enter(b=b):
if b.collision:
b.color = color._100
def on_mouse_exit(b=b):
if b.collision:
b.color = color.clear
b.on_mouse_enter = on_mouse_enter
b.on_mouse_exit = on_mouse_exit
def on_click(b=b):
# player turn
b.text = ’1′
b.color = color.black
b.collision = False
game.put(x=int(b.position.x), y=int(h – b.position.y – 1)) # start from top left
won_player = game.check_won()
if won_player > 0:
end_session(won_player)
game.next()
# cpu turn
input_o = game.board.board.copy()
input_o[(input_o != 1) & (input_o != 0)] = -1
input_o[(input_o == 1) & (input_o != 0)] = 1
input_o = np.expand_dims(input_o, axis=(0, -1)).astype(np.float32)
output = model.predict(input_o).squeeze()
output = output.reshape((h, w))
output_y, output_x = np.unravel_index(np.argmax(output), output.shape)
game.put(x=output_x, y=output_y)
data_x = str(output_x)
data_y = str(output_y)
print(data_x, data_y)
# 아두이노에 넘겨줄 데이터 정제
# 00/00 의 형태로 data_output 생성
if len(data_x) != 2:
data_x = ’0′ + data_x
if len(data_y) != 2:
data_y = ’0′ + data_y
data_output = “Q” + data_x + data_y
data_output = data_output.encode(‘utf-8′)
sendSerial.write(data_output)
ardSerial.write(data_output)
print(type(data_output))
print(data_output)
board_buttons[h - output_y - 1][output_x].text = ’2′
board_buttons[h - output_y - 1][output_x].text_color = color.black
board_buttons[h - output_y - 1][output_x].color = color.white
board_buttons[h - output_y - 1][output_x].collision = False
won_player = game.check_won()
if won_player > 0:
end_session(won_player)
game.next()
print(game.board)
b.on_click = on_click
def end_session(won_player):
Panel(z=1, scale=10, model=’quad’)
t = Text(f’Player {won_player} won!’, scale=3, origin=(0, 0), background=True)
t.create_background(padding=(.5,.25), radius=Text.size/2)
app.run()
영상인식 코드
import numpy as np, cv2
import time
image = cv2.imread(‘images/go2.jpg’, cv2.IMREAD_COLOR)
capture = cv2.VideoCapture(0) #카메라 불러오는 함수
if capture.isOpened() == False: raise Exception(“카메라 연결 안됨”)
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~
ret, image = capture.read() # image로 받아옴
mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함
blue_threshold = 70 # 흑백으로 바꾸는 기준 1
green_threshold = 70 # 흑백으로 바꾸는 기준 2
red_threshold = 70 # 흑백으로 바꾸는 기준 3
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌
for i in range(image.shape[0]):
for j in range(image.shape[1]):
if (image[i, j, 0] >= blue_threshold) and (image[i, j, 1] >= green_threshold) and image[i, j, 2] >= red_threshold:
mark_1[i, j ] = 255,255,255 # 검정
else :
mark_1[i, j ] = 0,0,0 # 하얀색
ju = cv2.imread(‘images/go2.jpg’, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문
template = white_s # template = 비교할 것
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이
res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함
threshold = 0.6 # 이 값보다 높을 때
loc = np.where(res > threshold) # 위치를 리턴함
for pt in zip(*loc[::-1]): # 위치의 가로 세로
if (image[pt[1], pt[0],2] != 255):
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0)) # 중심에 파란 원
s_x = int(pt[0] + w * 0.5) # 중심 x
s_y = int(pt[1] + h * 0.5) # 중심 y
s_xy = str(s_x) + str(s_y) # str로 합침
s_xy = int(s_xy) # 다시 숫자로 바꿈
s_xy_dt = []
stone_dt = []
for i in range(0, 10): # 범위 내 값을 걸러줌
for j in range(0, 10):
s_xy_dt.append(s_xy – i*1000 – j)
s_xy_dt.append(s_xy – i*1000 + j)
s_xy_dt.append(s_xy + i*1000 – j)
s_xy_dt.append(s_xy + i*1000 + j)
xx = 0
for k in range(len(s_xy_dt)): ## 값이 같지 않으면 stack
if stone != s_xy_dt[k]:
xx += 1
yy = 0
if xx == len(s_xy_dt) : # stack 값이 다 같을 때 = 오차의 범위 값이 list에 없을 때
if s_xy < 99999 : # y축이 두자리 프레임일 때
for k in range(len(stone)):
if abs(stone[k] – s_xy) < 110: # 100의 자리 = x축 프레임 차이, 1의 자리 = y축 프레임
yy += 1
if yy == 0 :
stone.append(s_xy)
else : # x ,y 둘다 3자리 프레임일 때
for k in range(len(stone)):
if abs(stone[k] – s_xy) < 1010: # 1000의 자리 = x축, 1의자리 = y축의 차이
yy += 1
if yy == 0 :
stone.append(s_xy)
aa = len(final_stone)
for i in range(len(stone)):
stone_xx_1 = 0
stone_yy_1 = 0
if stone[i] < 99999 :
stone_x = stone[i] / 100 # 앞자리 3개
stone_y = stone[i] % 100 # 뒷자리 2개
stone_y = abs(stone_y) / 20 # 경험적 치수
stone_x = (abs((stone_x – 160) / 20) ) # 경험적 치수
else :
stone_x = stone[i] / 1000 # 앞자리 3개
stone_y = stone[i] % 1000 # 뒷자리 3개
stone_x = (abs((stone_x – 160) / 20)) # 경험적 치수
stone_y = (stone_y / 20 ) # 경험적 치수
ab = int(stone_x) *100 + int(stone_y)
if ab not in final_stone: # list에 없으면
final_stone.append(ab)
if aa != len(final_stone) : # 전의 길이와 다르면 = 새롭게 추가되면
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))
print(‘——————’)
cv2.imshow(‘mm’, image) # 이미지 출력
cv2.imshow(‘aa’, ju) # 비교 이미지 출력
cv2.waitKey(150) # delay
5neck.py
import numpy as np, cv2
import time
image = cv2.imread(‘images/go2.jpg’, cv2.IMREAD_COLOR)
capture = cv2.VideoCapture(0) #카메라 불러오는 함수
if capture.isOpened() == False: raise Exception(“카메라 연결 안됨”)
stone = []
final_stone = []
while(capture.isOpened()): # 카메라가 열려있을 때 ~
ret, image = capture.read() # image로 받아옴
mark = np.zeros(image.shape, image.dtype) # mark 라는 친구를 생성함
mark_1 = np.zeros(image.shape, image.dtype) # mark_1이라는 친구도 생성함함
blue_threshold = 70 # 흑백으로 바꾸는 기준 1
green_threshold = 70 # 흑백으로 바꾸는 기준 2
red_threshold = 70 # 흑백으로 바꾸는 기준 3
bgr_threshold = [blue_threshold, green_threshold, red_threshold] # 3차월 배열로 바꿔줌
for i in range(image.shape[0]):
for j in range(image.shape[1]):
if (image[i, j, 0] >= blue_threshold) and (image[i, j, 1] >= green_threshold) and image[i, j, 2] >= red_threshold:
mark_1[i, j ] = 255,255,255
else :
mark_1[i, j ] = 0,0,0
#print(mark.shape)
ju = cv2.imread(‘images/go2.jpg’, cv2.COLOR_BGR2GRAY) # 검출할 사진 불러옴
white_s = cv2.cvtColor(ju, cv2.COLOR_BGR2GRAY) # 불러온 친구의 색 공간을 바꿈
mark = cv2.cvtColor(mark_1 , cv2.COLOR_BGR2GRAY) # 위와 동문
template = white_s # template = 비교할 것
w, h =template.shape[:: -1] # 불러온 사진의 너비와 높이
#stone = []
res = cv2.matchTemplate(mark, template, cv2.TM_CCOEFF_NORMED) # 함수를 사용해서 비교함
threshold = 0.6 # 이 값보다 높을 때
loc = np.where(res > threshold) # 위치를 리턴함
for pt in zip(*loc[::-1]): # 위치의 가로 세로
if (image[pt[1], pt[0],2] != 255):
cv2.rectangle(image, pt , (pt[0]+w, pt[1]+h), (0,255,0),2) # 초록색으로 칠함
cv2.circle(image, (int(pt[0]+w*0.5), int(pt[1]+h*0.5)),1, (255,0,0))
#print(int(pt[0]+w*0.5), int(pt[1]+h*0.5))
s_x = int(pt[0] + w * 0.5)
s_y = int(pt[1] + h * 0.5)
s_xy = str(s_x) + str(s_y)
s_xy = int(s_xy)
s_xy_dt = []
stone_dt = []
for i in range(0, 10):
for j in range(0, 10):
s_xy_dt.append(s_xy – i*1000 – j)
s_xy_dt.append(s_xy – i*1000 + j)
s_xy_dt.append(s_xy + i*1000 – j)
s_xy_dt.append(s_xy + i*1000 + j)
xx = 0
for k in range(len(s_xy_dt)):
if stone != s_xy_dt[k]:
xx += 1
yy = 0
if xx == len(s_xy_dt) :
if s_xy < 99999 :
for k in range(len(stone)):
if abs(stone[k] – s_xy) < 110:
yy += 1
if yy == 0 :
stone.append(s_xy)
else :
for k in range(len(stone)):
if abs(stone[k] – s_xy) < 1010:
yy += 1
if yy == 0 :
stone.append(s_xy)
#print(stone)
aa = len(final_stone)
for i in range(len(stone)):
stone_xx_1 = 0
stone_yy_1 = 0
if stone[i] < 99999 :
stone_x = stone[i] / 100
stone_y = stone[i] % 100
#print(stone_x)
#print(stone_y)
stone_y = abs(stone_y) / 20
stone_x = (abs((stone_x – 160) / 20) )
else :
stone_x = stone[i] / 1000
stone_y = stone[i] % 1000
#print(stone_x)
#print(stone_y)
stone_x = (abs((stone_x – 160) / 20))
stone_y = (stone_y / 20 )
ab = int(stone_x) *100 + int(stone_y)
if ab not in final_stone:
final_stone.append(ab)
#for k in range(len(final_stone)):
# print(int(final_stone[k] / 100) , int(final_stone[k] % 100))
if aa != len(final_stone) :
print( int(final_stone[-1] % 100), int(final_stone[-1]/100))
print(‘——————’)
cv2.imshow(‘mm’, image)
cv2.imshow(‘aa’, ju)
cv2.waitKey(150)
Omok.py
import numpy as np
class Board():
def __init__(self, w, h):
self.w = w
self.h = h
self.board = np.zeros((self.h, self.w), dtype=np.int)
“”"
def __repr__(self):
string = ”
data1 = []
data2 = []
for y in range(self.h):
for x in range(self.w):
string += ‘%d ‘ % self.board[y][x]
if self.board[y][x] == 2:
data1.append(x)
data2.append(y)
string += ‘\n’
return string
“”"
class Omok():
def __init__(self, board):
self.board = board
self.current_player = 1
self.won_player = 0
def reset(self):
self.board.board = 0
self.current_player = 1
self.won_player = 0
def put(self, x=None, y=None):
if x is None and y is None:
while True:
rand_x = np.random.randint(0, self.board.w)
rand_y = np.random.randint(0, self.board.h)
if self.board.board[rand_y][rand_x] == 0:
self.board.board[rand_y][rand_x] = self.current_player
break
else:
self.board.board[y][x] = self.current_player
def next(self):
if self.current_player == 1:
self.current_player = 2
else:
self.current_player = 1
def check_won(self):
player = self.current_player
for y in range(self.board.h):
for x in range(self.board.w):
try:
if self.board.board[y][x] == player and self.board.board[y + 1][x] == player and \
self.board.board[y + 2][x] == player and self.board.board[y + 3][x] == player and \
self.board.board[y + 4][x] == player:
self.won_player = player
break
except:
pass
try:
if self.board.board[y][x] == player and self.board.board[y][x + 1] == player and \
self.board.board[y][x + 2] == player and self.board.board[y][x + 3] == player and \
self.board.board[y][x + 4] == player:
self.won_player = player
break
except:
pass
try:
if self.board.board[y][x] == player and self.board.board[y + 1][x + 1] == player and \
self.board.board[y + 2][x + 2] == player and self.board.board[y + 3][x + 3] == player and \
self.board.board[y + 4][x + 4] == player:
self.won_player = player
break
except:
pass
try:
if x >= 4 and self.board.board[y][x] == player and self.board.board[y + 1][x - 1] == player and \
self.board.board[y + 2][x - 2] == player and self.board.board[y + 3][x - 3] == player and \
self.board.board[y + 4][x - 4] == player:
self.won_player = player
break
except:
pass
if self.won_player > 0:
break
return self.won_player
모델 학습 코드
import numpy as np
import tensorflow as tf
import tensorflow.keras as keras
from tensorflow.keras import layers, models
from sklearn.model_selection import train_test_split
from glob import glob
from tqdm import tqdm
from datetime import datetime
from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau
import os
w, h = 20, 20
file_list = glob(‘C:\JupyterProject\dataset_2020\*.npz’)
x_data, y_data = [], []
for file_path in tqdm(file_list):
data = np.load(file_path)
x_data.extend(data['inputs'])
y_data.extend(data['outputs'])
x_data = np.array(x_data, np.float32).reshape((-1, h, w, 1))
y_data = np.array(y_data, np.float32).reshape((-1, h * w))
x_train, x_val, y_train, y_val = train_test_split(x_data, y_data, test_size=0.2, random_state=2020)
del x_data, y_data
print(x_train.shape, y_train.shape)
print(x_val.shape, y_val.shape)
model = models.Sequential([
layers.Conv2D(64, 7, activation='relu', padding='same', input_shape=(h, w, 1)),
layers.Conv2D(128, 7, activation='relu', padding='same'),
layers.Conv2D(256, 7, activation='relu', padding='same'),
layers.Conv2D(128, 7, activation='relu', padding='same'),
layers.Conv2D(64, 7, activation='relu', padding='same'),
layers.Conv2D(1, 1, activation=None, padding='same'),
layers.Reshape((h * w,)),
layers.Activation('sigmoid')
])
model.compile(
optimizer=’adam’,
loss=’binary_crossentropy’,
metrics=['acc']
)
model.summary()
start_time = datetime.now().strftime(‘%Y%m%d_%H%M%S’)
os.makedirs(‘models’, exist_ok=True)
model.fit(
x=x_train,
y=y_train,
batch_size=256,
epochs=10,
callbacks=[
ModelCheckpoint('./models/%s.h5' % (start_time), monitor='val_acc', verbose=1, save_best_only=True, mode='auto'),
ReduceLROnPlateau(monitor='val_acc', factor=0.2, patience=5, verbose=1, mode='auto')
],
validation_data=(x_val, y_val),
use_multiprocessing=True,
workers=16
)
[67호]메타버스를 이용한 문서 보안 시스템
2021 ICT 융합 프로젝트 공모전 최우수상
메타버스를 이용한 문서 보안 시스템
글 | 광운대학교 최예지, 최혁순
1.심사평
칩센 우선 Leap Motion과 unity을 통한 가상현실을 이용하는 것이 실제로는 처음 보고, 매우 흥미로웠습니다. 일반적인 실제 사용의 단계라면 어쩌면 번거로울 수도 있겠으나, 작품의 기획 의도인 강화된 보안이 필요한 경우에는 매우 유용한 기술로 판단됩니다. 다단계의 보안 단계를 만들고, AWS를 이용하여 떨어져 있는 다중의 사용자들 간에 보안 적용된 특정 정보 (작품에서는 문서)의 접근 권한을 만들어주는 것 또한 익숙하지 않은 저에게는 매우 놀라웠습니다. 세부적인 기술 내역에 대하여 저도 검토해봐야겠다는 생각이 드는 작품입니다. 다만 소프트웨어적인 구현만으로 이루어진 점은 개인적으로 조금 아쉽고, 동일한 보안 해제 방식을 통해 특정 목적을 가진 기기를 컨트롤할 수 있는 형태라면 기술의 범위가 충분히 확장될 수 있지 않을까 합니다.
펌테크 날로 중요시되는 컴퓨터 보안에 메타버스 개념을 접목한 독창적이고 색다른 형태의 작품으로 핵심요소인 소프트웨어 개발에 따른 각각의 SDK, 개발툴 등을 작품의 성격에 맞게 효율적으로 구성하였으며 전체적으로 작품의 기획의도, 기술 구현도, 완성도 등에서 우수한 작품이라고 생각됩니다.
위드로봇 Leap motion을 이용한 제스처로 보안을 처리한 아이디어가 돋보이는 작품입니다. 공모전의 성격을 감안하여 LeapMotion을 대치할 수 있는 기술까지 같이 연구가 진행되었다면 더욱 훌륭한 연구가 되었을 것 같습니다.
뉴티씨 매우 독특한 발상으로 잘 만들어진 작품입니다. 스마트폰에만 의존하는 보안 시스템에서 발전하여 Cloud server를 활용하여 보다 높은 수준의 보안 시스템을 구현한 예가 될 수 있습니다. 작품의 모양이나 시스템의 표현 방법이나 사용 방법 등을 대폭 개선하면, 좋은 제품으로 탄생할 수도 있을 것 같습니다. 아파트 현관이나 특정 사무실의 보안 시스템도 그런 식으로 만드는 것도 방법인 것 같습니다. 앞으로 기대가 됩니다.
2. 작품 개요
개인 휴대폰을 이용한 문서 공유, 개인 정보 저장, 신상 정보를 이용한 거래 등의 서비스들이 늘어나고 있다. 등본이나 각종 증명서, 회사 기업 자료 등 중요한 문서 파일을 휴대폰에 저장하기도 하고, 회원가입 정보나 여러 사이트의 비밀번호, 개인 정보를 휴대폰에 저장하기도 하며, 인터넷 뱅킹, 휴대폰 결제와 같은 개인정보를 이용한 서비스들을 휴대폰을 통해 사용한다. 이러한 휴대폰 속 개인정보를 이용한 서비스들과 휴대폰에 저장하는 개인정보, 주요 문서들이 늘어남에 따라 이를 악용한 범죄 또한 늘어나고 있다. 이러한 범죄에 우리가 노출됨에 있어 가장 큰 문제는 오직 핸드폰의 보안에만 의존하고 있다는 점이다. 휴대폰에 저장된 대부분의 주요 문서나 개인 정보가 오직 핸드폰의 보안 또는 어플 자체의 보안에만 의존하고 있다. 우리는 이러한 문제점을 인식하고 더욱 함양된 보안성을 갖춘 문서 보안 시스템을 구축하기 위해 메타버스를 이용해보기로 했다. 메타버스를 이용한 보안 시스템의 취약성은 아직 발견된 것이 없으며, 컴퓨터와 휴대폰 두 개의 디바이스를 이용하여 보안 시스템을 구축하기 때문에 더 높은 보안 장벽을 갖는다는 장점이 있다. 이러한 장점을 가진 메타버스 중 우리는 Leap Motion과 Unity를 이용해 시스템을 구축하기로 했다. Leap Motion은 손의 움직임을 인식하는 장치로 이를 이용하면 오직 손의 움직임만을 이용하여 잠금을 해제하는 보안장치를 만들 수 있기 때문에 잠금을 해제하는 기록을 데이터로 남기기 어려워 보안에 유리하다. 또한 Unity를 이용한 3D 가상현실을 만들어 여기에 Leap Motion을 적용한다면, 보안 장치를 사용하는 사용자가 아닌 이상 보안 장치 해제 방식을 파악하기 어려워 보안에 유리하다. 우리는 이러한 장점을 이용하여 Leap Motion, Unity를 이용한 메타버스 문서 보안 시스템을 제작하기로 했다.
3. 작품 설명
3.1. 주요 동작 및 특징
시스템은 Leap Motion을 이용한 1차 보안과 Unity와 앱을 이용한 2차 보안, 1차 보안과 2차 보안 사이의 서버 통신으로 이루어져 있다.
Leap Motion을 이용해 손의 동작을 감지하고 정해진 동작이 수행되면 웹 서버인 AWS DynamoDB로 동작이 수행됨을 알리는 정보가 넘어간다. 서버 통신을 통해 Leap Motion을 이용한 1차 보안이 해제됨을 감지한 AWS DynamoDB는 이 정보를 핸드폰에 설치된 어플로 넘긴다. 어플에서 정보를 받으면 Unity와 앱을 이용한 2차 보안이 실행되고, 2차 보안에서 정해진 순서대로 번호를 터치하게 되면 문서의 잠금이 해제된다.
Leap Motion을 이용한 1차 보안
Leap Motion에 정해진 동작을 수행하면 그 동작에 해당하는 마법진이 생성되고, 정해진 손짓을 통해 생성된 마법진을 뒤로 보낼 수 있다. 정해진 동작을 순서대로 수행하면 그 동작에 해당하는 4종류의 마법진이 순서대로 생성되고, 이 4종류의 마법진을 정해진 손짓을 통해 순서대로 뒤로 보내 circle에 통과시키면 보안이 해제된다. 이때 4종류의 마법진을 정해진 순서대로 circle에 통과시키지 않으면 보안 잠금이 해제되지 않는다.
[코드 가-1] 위 사진에 해당하는 코드
if ((frame.Hands[1].GrabStrength == 1.0) && (frame.Hands[1].PinchStrength == 1.0)) //왼손 grab 후 pinch
{
count += 1;
if (count > 3)
count = 0;
}
if (count == 0)
{
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 손가락 pinch
{
fstone.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && fstone.state == 1) //윗줄 후 왼손, 오른손 no pinch
{
GameObject realplz = Instantiate(fstobj) as GameObject; // 첫 번째 오브젝트 생성
realplz.transform.position = new Vector3(0, 0.5f,0.5f);
fstone.state = 0;
}
}
그림 가-1과 같이 양 손을 pinch 후 떼어내면 마법진이 생성된다.
[코드 가-2] 위 사진에 해당하는 코드
if ((frame.Hands[1].GrabStrength == 1.0) && (frame.Hands[1].PinchStrength == 1.0)) //왼손 grab 후 pinch{
count += 1;
if (count > 3)
count = 0;
}
if (count == 0){
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 손가락 pinch{
fstone.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && fstone.state == 1) //윗줄 후 왼손, 오른손 no pinch{
GameObject realplz = Instantiate(fstobj) as GameObject; // 첫 번째 오브젝트 생성
realplz.transform.position = new Vector3(0, 0.5f,0.5f);
fstone.state = 0;
}
}
else if (count == 1){
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 pinch{
second.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && second.state == 1) //윗 줄 후 왼손, 오른손 no pinch{
GameObject realplz = Instantiate(scdobj) as GameObject; //두 번째 오브젝트 생성
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);
second.state = 0;
}
}
else if (count == 2)
{
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 pinch{
third.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && third.state == 1) //윗 줄 후 왼손, 오른손 no pinch{
GameObject realplz = Instantiate(thdobj) as GameObject; //세 번째 오브젝트 생성
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);
third.state = 0;
}
else if (count == 3)
{
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0) //왼손, 오른손 pinch{
fourth.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && fourth.state == 1) //윗 줄 후 왼손, 오른손 no pinch{
GameObject realplz = Instantiate(fthobj) as GameObject; //네 번째 오브젝트 생성
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);
fourth.state = 0;
}
}
왼손 grab 후 오른손 pinch 한 번 할 때마다 object, 즉 마법진이 변경된다.
[코드 가-3] 위 사진에 해당하는 코드
if (HandPalmYam > -2f && HandPalmYam < 3.5f && (frame.Hands[1].GrabStrength==1.0 )) //왼손 grab 후 오른손 흔듦{
Debug.Log(“앞”);
this.transform.Translate(0, 2 * Time.deltaTime,0); //오브젝트 앞으로 이동
}
if ((frame.Hands[1].GrabStrength == 1.0) && (frame.Hands[0].GrabStrength == 1.0)) //양손 grab
Destroy(gameObject); //오브젝트 사라짐
왼손 grab 후 오른손 흔들면 object, 즉 마법진이 앞으로 이동하다가 circle에 닿으면 사라지게 된다. 이때, 마법진 4종류가 정해진 순서대로 circle에 닿아야만 잠금이 해제된다.
Object, 즉 마법진은 계속해서 회전하고 있다.
코드 가-5에 의해 object, 즉 마법진이 circle에 닿으면 사라지게 된다. 또한 4종류의 마법진이 정해진 순서대로 circle에 닿아야만 보안이 해제된다.
1차 보안과 2차 보안 사이의 서버 통신
Leap Motion을 이용한 1차 보안이 해제됨을 알리는 정보가 AWS DynamoDB로 넘어간다. AWS DynamoDB는 받은 정보를 다시 2차 보안에 사용되는 앱에 넘기고, 앱은 정보를 받으면 보안 해제 시스템이 작동된다. 2차 보안 앱은 1차 보안이 해제되기 전까지는 실행되지 않고, 로딩창에 머무르게 된다. 1차 보안이 해제되었다는 정보를 넘겨받아야 실행이 되는 구조이다.
[그림 나-2]는 DynamoDB에서 leapmotion의 데이터가 초기설정 값인 0000이었지만, 1차 보안을 해제한 후 1234로 바뀐 모습이다.
private void Start() //AWS에서 계정에 대한 인증과 권한을 부여받는 코드이다.
{
UnityInitializer.AttachToGameObject(this.gameObject);
credentials = new CognitoAWSCredentials(“ap-northeast-2:ceafc145-14a2-4c03-b397-22368bc8ab4f”, RegionEndpoint.APNortheast2);
DBclient = new AmazonDynamoDBClient(credentials, RegionEndpoint.APNortheast2);
context = new DynamoDBContext(DBclient);
}
{
[DynamoDBHashKey] // Hash key.
public string id { get; set; }
[DynamoDBProperty] public int item { get; set; }
}
public void FindItem() //DB에서 캐릭터 정보 받기
{
context.LoadAsync<Character>(“leapmotion”, (AmazonDynamoDBResult<Character> result) =>
{
// id가 leapmotion인 캐릭터 정보를 DB에서 받아옴
if (result.Exception != null)
{
Debug.LogException(result.Exception);
// 정보를 받아오지 못했을 때의 예외처리
return;
}
c = result.Result;
Debug.Log(c.item); //찾은 캐릭터 정보 중 아이템 정보 출력
}, null);
}
void Update()
{
FindItem();
if (c!=null) //앱 실행 후 초기화되는 aws에서 정보를 받아오는 변수이므로 받아온 정보가 null값인지를 먼저 확인한다.
{
if (c.item == 1234)
{
GameObject.Destroy(this.gameObject);
SceneManager.LoadScene(“realA”);
}
}
Unity와 앱을 이용한 2차 보안
AWS DynamoDB로 전달된 1차 보안이 해제됨을 알리는 정보는 만들어둔 핸드폰에 전달된다. 정보를 받은 핸드폰은 앱을 실행시킨다. 실행된 앱에서 정해진 물체에 카메라를 가져가면 키패드가 생성되고, 생성된 키패드에 비밀번호를 순서대로 입력하면 2차 보안이 해제되어 문서를 열람할 수 있게 된다.
AR 키패드 화면으로 넘어왔지만, 지정된 물체를 비추지 않아, 키패드가 나타나지 않는 모습이다.
지정된 물체를 비추고 나서, 키패드가 나타나는 모습이다.
if (count == 4) //사용자가 키패드를 누른 횟수를 센다
{
Debug.Log(“full”);
for (int i = 0; i < 4; i++)
{
if (answer[i] != realanswer[i]) //입력 비밀번호랑 정답 비밀번호랑 비교
{
SceneManager.LoadScene(“retry”);
break;
}
3.2. 전체 시스템 구성
위 시스템은 Leap Motion을 이용한 1차 보안과 Unity와 앱을 이용한 2차 보안, 1차 보안과 2차 보안 사이의 서버 통신으로 이루어져 있다.
Leap Motion을 이용해 손의 동작을 감지하고 정해진 동작이 수행되면 웹 서버인 AWS DynamoDB로 동작이 수행됨을 알리는 정보가 넘어간다. 서버 통신을 통해 Leap Motion을 이용한 1차 보안이 해제됨을 감지한 AWS DynamoDB는 이 정보를 핸드폰에 설치된 어플로 넘긴다. 어플에서 정보를 받으면 Unity와 앱을 이용한 2차 보안이 실행되고, 2차 보안 앱에서 지정된 물체를 비추게 되면 키패드가 나타나고, 키패드에 비밀번호를 입력하게 되면 2차 보안이 해제되면서 문서의 잠금이 해제된다. 따라서 키패드를 거치기 전, 지정된 물체를 알고 있어야 하고, 그것을 비추어야만 비밀번호를 입력할 수 있다는 점에서 2차 보안 속에 한 단계의 보조 보안단계가 더 있다고 볼 수 있다.
3.3. 개발 환경
위 시스템은 Leap Motion을 이용한 1차 보안과 Unity와 앱을 이용한 2차 보안, 1차 보안과 2차 보안 사이의 서버 통신으로 이루어져 있다. 먼저 Leap Motion을 이용한 1차 보안에서 Leap Motion과 Unity를 이용하며, 이를 위해 Leap Motion SDK와 개발 Tool로 Unity 2D, Unity 3D를 사용하였다. 또한 Unity 개발 언어로 C#을 이용하였으며 3D 가상 현실 환경을 구축하기 위해 Asset store를 사용하였다. 또한 개발 언어를 위한 Tool로 Visual Studio를 사용하였다. 다음으로 1차 보안과 2차 보안 사이의 서버 통신을 위한 시스템으로 AWS와 DynamoDB를 사용하였다. 마지막으로 App과 Unity를 이용한 2차 보안에서 App 개발과 보안 환경 구축을 위해 개발 Tool로 Unity 2D, Unity 3D를 사용하였다. 또한 Unity 개발 언어로 C#을 이용하였으며 3D 가상 현실 환경을 구축하기 위해 Asset store와 Vuforia를 사용하였다. 또한 개발 언어를 위한 Tool로 Visual Studio를 사용하였다.
Leap Motion을 이용한 1차 보안
생체 움직임 감지를 위해 Leap Motion을 사용하였으며, Leap Motion을 사용하기 위해 Leap Motion SDK를 사용하였다. Leap Motion 이용을 위한 증강 현실을 구현하기 위한 개발 Tool로 Unity 2D와 Unity 3D를 사용하였으며, Unity 개발 언어로 C#을 사용하고, 개발 언어를 사용하기 위한 Tool로 Visual Studio를 사용하였다. 3D 증강 현실 구현을 위해 Asset Store를 사용하였으며, AR엔진으로 Vuforia를 이용하였다.
1차 보안과 2차 보안 사이의 서버 통신
1차 보안에서의 정보를 서버로 옮기고, 서버에 저장된 정보를 다시 휴대폰 앱으로 전송하기 위한 웹 서버를 구축하였다. 이때 AWS를 사용하여 웹서버를 구축하였다. 웹 서버로 통신한 정보를 저장하기 위해 DynamoDB를 데이터베이스로 사용하였다.
Unity와 App을 이용한 2차 보안
증강 현실을 이용한 App 개발을 위한 개발 Tool로 Unity 2D와 Unity 3D를 사용하였으며, Unity 개발 언어로 C#을 사용하고, IDE로 Visual Studio를 사용하였다. 3D 증강 현실 구현을 위해 Asset Store를 사용하였으며, AR엔진으로 Vuforia를 이용하였다.
4. 단계별 제작 과정
시스템은 ‘Leap Motion을 이용한 1차 보안’과 ‘Unity와 앱을 이용한 2차 보안’, ‘1차 보안과 2차 보안 사이의 서버 통신’으로 단계를 나누어 제작하였다.
4.1. Leap Motion을 이용한 1차 보안
이 단계에서는 먼저 Leap Motion을 Unity와 연동시킨 뒤 Leap Motion의 동작 인식 감도를 확인하였다. 이후 Unity를 이용해 Leap Motion을 이용하기 위한 3D 가상 현실과 보안 시스템을 구축하였고, 가상 현실과 보안 시스템을 제어하기 위한 코드를 작성하였다.
그림 4-1-1과 같이 Leap Motion을 이용하기에 앞서 Leap Motion을 Unity에 연동시키고, Unity에서의 Leap Motion 작동 여부와 실제 작동 환경을 테스트하였다. 이후 Leap Motion의 손동작 인식 감도를 확인하였다.
다음으로 그림 4-4-2와 같이 Leap Motion이 사용되는 가상 현실을 Unity를 통해 구축하였다. 배경과 바닥을 3D object와 asset을 이용하여 구현했으며, 손동작에 따라 생성되고 작동될 3D object를 만들었다. 마지막으로 코드 3-1-가-1,2,3,4,5와 같이 Leap Motion에 감지된 동작에 따라 실행될 기능에 대해 코드를 작성하였다. 왼손 grab 후에 pinch 한 번 할 때마다 object가 바뀌고, 양손을 pinch 후에 떼면 object가 생성되는 코드, 왼손 grab 후 오른손을 흔들면 object가 앞으로 이동하는 코드, 양손 grab시에 object가 사라지는 코드, object가 회전하는 코드, object가 circle에 닿으면 사라지는 코드, object가 정해진 순서대로 circle에 닿으면 그 정보를 관리자 object에 전달하는 코드를 작성하였다.
4.2. 1차 보안과 2차 보안 사이의 서버 통신
이 단계에서는 먼저 1차 보안을 담당하는 노트북과 2차 보안을 담당하는 핸드폰 App사이의 서버 통신을 위한 웹서버를 구축하였다. 이후 노트북의 정보가 구축한 웹서버로 전달되는지 여부를 확인하였다. 다음으로 웹서버에 저장된 데이터 베이스가 휴대폰 App으로 전달되는지 여부를 확인하였다. 마지막으로 1차 보안이 해제됨을 알리는 정보를 웹 서버에 전달하고 웹 서버는 이를 데이터 베이스에 저장하여 2차 보안을 담당하는 휴대폰 App으로 전달하는 코드를 작성하였다.
그림 4-2-1과 같이 웹서버를 구축하였다. DynamoDB에 leapmotion 데이터가 저장되어 있는 모습을 볼 수 있다.
그림 4-2-2와 같이 1차 보안과 웹 서버 사이의 서버 통신이 원활하게 이루어지는지 확인하였다.
그림 4-2-3과 같이 웹 서버에 저장된 데이터 베이스가 2차 보안을 담당하는 휴대폰 App으로 잘 전달되는지 여부를 확인하였다. DynamoDB의 데이터 leapmotion의 정보를 잘 받았는지 확인하는 콘솔로그 창이다.
코드 4-2-1과 같이 1차 보안이 해제됨을 알리는 정보를 웹 서버에 전달하고 웹 서버는 이를 데이터 베이스에 저장하여 2차 보안을 담당하는 휴대폰 App으로 전달하는 코드를 작성하였다.
4.3. Unity와 App을 이용한 2차 보안
이 단계에서는 노트북에서 Unity를 이용하여 2차 보안의 가상 현실을 만든 뒤, 순서대로 정해진 비밀번호를 입력하면 문서 보안이 해제되는 코드를 작성하였다. 이후 제작한 App을 휴대폰에 복제하였다. 마지막으로 제작한 App이 구축한 웹 서버를 통해 1차 보안이 해제됨을 알리는 데이터 베이스를 전달하면 App이 실행되도록 하는 코드를 작성하였다.
그림 4-3-1과 같이 Unity를 이용하여 2차 보안에 사용할 가상 현실을 제작했다.
그림 4-3-2와 같이 앞서 노트북으로 만든 App을 휴대폰에 빌드하여 휴대폰으로 App이용이 가능하게 만들었다.
5. 사용한 제품 리스트
‘Leap Motion을 이용한 1차 보안’과 ‘Unity와 앱을 이용한 2차 보안’, ‘1차 보안과 2차 보안 사이의 서버 통신’으로 나누어져 있다. 이 중 Leap Motion을 이용한 1차 보안 단계에서 Leap Motion Controller제품을 사용하였다.
6. 회로도
작품의 전체 회로도는 그림 6-1과 같다. Leap Motion과 노트북이 1차 보안을 위해 연결되어 있으며, 2차 보안인 휴대폰 App과 웹 서버를 통해 연결되어 있다.
7. 소스코드
7.1. Leap Motion을 이용한 1차 보안 코드
Leap Motion Hand model 코드
void Update(){
controller = new Controller();
Frame frame = controller.Frame();
List<Hand> hands = frame.Hands;
Frame previous = controller.Frame(1);
Hand previous_leapHand = previous.Hands[0];
Vector handOrigin = frame.Hands[0].PalmPosition;
Vector previoushandOrigin = previous_leapHand.PalmPosition;
if ((frame.Hands[1].GrabStrength == 1.0) && (frame.Hands[1].PinchStrength == 1.0)){
count += 1;
if (count > 3)
count = 0;
}
if (count == 0) {
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0)
{
fstone.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && fstone.state == 1) {
GameObject realplz = Instantiate(fstobj) as GameObject;
realplz.transform.position = new Vector3(0, 0.5f,0.5f);
fstone.state = 0;
}
}
else if (count == 1){
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0){
second.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && second.state == 1){
GameObject realplz = Instantiate(scdobj) as GameObject;
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);
second.state = 0;
}
}
else if (count == 2) {
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0){
third.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && third.state == 1){
GameObject realplz = Instantiate(thdobj) as GameObject;
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);
third.state = 0;
}
else if (count == 3){
if (frame.Hands[0].PinchStrength == 1.0 && frame.Hands[1].PinchStrength == 1.0){
fourth.state = 1;
}
if (frame.Hands[0].PinchStrength < 1.0 && frame.Hands[1].PinchStrength < 1.0 && fourth.state == 1){
GameObject realplz = Instantiate(fthobj) as GameObject;
realplz.transform.position = new Vector3(0, 0.5f, 0.5f);
fourth.state = 0;
}
}
strength1 = frame.Hands[0].GrabStrength;
strength2 = frame.Hands[1].GrabStrength;
Debug.Log(“왼손??” + strength2);
Debug.Log(“오른손??” + strength1);
}
}
Leap Motion New Behavior 코드
Controller controller;
float HandPalmPitch;
float HandPalmRoll;
float HandPalmYam;
float HandWristRot;
Rigidbody rb;
void Start(){
}
void Update(){
controller = new Controller();
Frame frame = controller.Frame();
List<Hand> hands = frame.Hands;
if (frame.Hands.Count > 0){
Hand fristHand = hands[0];
}
HandPalmPitch = hands[0].PalmNormal.Pitch;
HandPalmRoll = hands[0].PalmNormal.Roll;
HandPalmYam = hands[0].PalmNormal.Yaw;
HandWristRot = hands[0].WristPosition.Pitch;
Debug.Log(“Pitch : ” + HandPalmPitch);
Debug.Log(“Roll : ” + HandPalmRoll);
Debug.Log(“Yam : ” + HandPalmYam);
if (HandPalmYam > -2f && HandPalmYam < 3.5f && (frame.Hands[1].GrabStrength==1.0 )){
Debug.Log(“앞”);
this.transform.Translate(0, 2 * Time.deltaTime,0);
}
this.transform.Rotate(0, 30, 0);
if ((frame.Hands[1].GrabStrength == 1.0) && (frame.Hands[0].GrabStrength == 1.0))
Destroy(gameObject);
}
}
Leap Motion 관리자 object 코드
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class c1direc : MonoBehaviour{
GameObject director;
void Start(){
director = GameObject.Find(“director”);
}
void Update(){
if (this.transform.position.z > 3){
director.GetComponent<director>().arr[director.GetComponent<director>().ind] = 0;
director.GetComponent<director>().ind += 1;
}
}
}
7.2. 1차 보안과 2차 보안 사이의 서버 통신 코드
Leap Motion 서버 통신 director 코드
public class director : MonoBehaviour
{
int count;
public int [] arr = { -1, -1, -1, -1 };
int[] arranswer = { 0, 1, 2, 3 };
DynamoDBContext context;
AmazonDynamoDBClient DBclient;
CognitoAWSCredentials credentials;
Character c;
public Camera a;
private void Start()
{
UnityInitializer.AttachToGameObject(this.gameObject);
credentials = new CognitoAWSCredentials(“ap-northeast-2:ceafc145-14a2-4c03-b397-22368bc8ab4f”, RegionEndpoint.APNortheast2);
DBclient = new AmazonDynamoDBClient(credentials, RegionEndpoint.APNortheast2);
context = new DynamoDBContext(DBclient);
}
[DynamoDBTable("character_info")]
public class Character
{
[DynamoDBHashKey] // Hash key.
public string id { get; set; }
[DynamoDBProperty]
public int item { get; set; }
}
private void CreateCharacter() //캐릭터 정보를 DB에 올리기
{
Character c1 = new Character
{
id = “leapmotion”,
item = 1234,
};
context.SaveAsync(c1, (result) =>
{
if (result.Exception == null)
Debug.Log(“Success!”);
else
Debug.Log(result.Exception);
});
}
void Update() //써클을 통과한 원이 지정된 패턴과 일치하는지 확인 후, 일치하면 서버에 정보를 보냄
{
for(int i = 0; i <= 3; i++)
{
if (arr[i] == arranswer[i])
{
count += 1;
}
else if (arr[i] != arranswer[i])
break;
}
if (count== 4)
{
CreateCharacter();
Debug.Log(“카운트!!!!!!!!!!!!!!!!!111″);
}
7.3. Unity와 App을 이용한 2차 보안 코드
서버와 통신을 하는 direc코드
private void Start() //AWS에 접속해서 인증받고 권한을 받아오는 단계
{
UnityInitializer.AttachToGameObject(this.gameObject);
credentials = new CognitoAWSCredentials(“ap-northeast-2:ceafc145-14a2-4c03-b397-22368bc8ab4f”, RegionEndpoint.APNortheast2);
DBclient = new AmazonDynamoDBClient(credentials, RegionEndpoint.APNortheast2);
context = new DynamoDBContext(DBclient);
}
[DynamoDBTable("character_info")] public class Character{
[DynamoDBHashKey] // Hash key.
public string id { get; set; }
[DynamoDBProperty] public int item { get; set; }
}
public void FindItem() //DB에서 캐릭터 정보 받기
{
context.LoadAsync<Character>(“leapmotion”, (AmazonDynamoDBResult<Character> result) =>
{
// id가 leapmotion인 캐릭터 정보를 DB에서 받아옴
if (result.Exception != null)
{
Debug.LogException(result.Exception);
return;
}
c = result.Result;
Debug.Log(c.item); //찾은 캐릭터 정보 중 아이템 정보 출력
}, null);
}
void Update()
{
FindItem();
if (c!=null) //앱 실행 후 초기화되는 aws에서 정보를 받아오는 변수이므로 받아온 정보가 null값인지를 먼저 확인한다.
{
if (c.item == 1234)
{
GameObject.Destroy(this.gameObject);
SceneManager.LoadScene(“realA”);
} }}
입력한 비밀번호를 확인하는 director 코드
public class director : MonoBehaviour
{
public int[] answer = new int[4] { -1, -1, -1, -1 }; //입력받을 비밀번호를 저장할 배열
int[] realanswer = new int[4] { 2, 1, 3, 0 }; //원래 비밀번호 정답
public int count = 0;
int point = 0;
void Start()
{
a.enabled = true;
iinteraction = GameObject.Find(“iinteraction”);
interaction1 = GameObject.Find(“interaction1″);
interaction2 = GameObject.Find(“interaction2″);
interation3 = GameObject.Find(“interation3″);
}
// Update is called once per frame
void Update()
{
Debug.Log(count);
if (count == 4) //네개의 비밀번호를 입력받으면 일치하는 비밀번호인지 확인하는 코드
{
Debug.Log(“full”);
for (int i = 0; i < 4; i++)
{
if (answer[i] != realanswer[i])
{
SceneManager.LoadScene(“retry”);
break;
}
}
}
if (point ==4)
SceneManager.LoadScene(“EndScene”);
}
어떤 키패드가 눌렸는지 확인하는 interaction1 코드
void Start()
{ director = GameObject.Find(“director”);
mat = GetComponent<Renderer>().material;
mat.SetFloat(“_Mode”, 2);
mat.SetInt(“_SrcBlend”, (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
mat.SetInt(“_DstBlend”, (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
mat.SetInt(“_ZWrite”, 0);
mat.DisableKeyword(“_ALPHATEST_ON”);
mat.EnableKeyword(“_ALPHABLEND_ON”);
mat.DisableKeyword(“_ALPHAPREMULTIPLY_ON”);
mat.renderQueue = 3000;
defaultColor = new Color32(255, 255, 255, 255);
selectedColor = new Color32(255, 0, 0, 255);
mat.color = defaultColor;
}
void Update()
{
if (die == 1)
{
Destroy(gameObject);
}
}
void touchBegan() //키패드의 어떤 숫자가 눌렸는지 확인하는 코드
{
director.GetComponent<director>().count+=1;
director.GetComponent<director>().answer[director.GetComponent<director>().count-1] = 1;
8. 참고 문헌
[1] https://bit.ly/3wcnOow
[2] https://bit.ly/2QNPyjk
[3] https://bit.ly/31u0aWz
[4] https://amzn.to/39rTzQU
[5] https://bit.ly/3wfrber
[6] https://amzn.to/3dneDJx
[7] https://bit.ly/31y7Abi