보헤미안 랩소디가 한국에서 울려퍼지다 - 현대카드 슈퍼콘서트 25 QUEEN

흔치않은일상 2020. 1. 19. 17:14


 지난 2018년 10월 국내에 첫 개봉된 영화 '보헤미안 랩소디'를 통해 영국 락밴드 Queen의 인기가 폭발하였던 기억이 나네요. 무려 40여년 전에 세상에 나온 노래 ' Bohemian Rhapsody'가 이곳 저곳에서 울려퍼졌습니다. 뿐만 아니라 Queen의 대표곡들이 카페와 버스 안에서 라디오를 통해 전파를 타고 사람들이 이토록 팝송에 열과하는 현상을 유래가 없을 정도였지요.

 그렇게 한 시기의 유행처럼 지나가 잠시 기억에서 잊혀져 갈 때 즈음 반가운 소식을 듣게 되었습니다. Queen의 내한이 확정되어 2020년 1월 18일과 19일 공연을 하게 된 것이지요.

 저도 영화 보헤미안 랩소디 영화를 보고난 후 퀸의 공연을 해외에서 직접 관람해보고 싶었던 꿈을 갖고 있었는데 이렇게 한국을 찾아와 공연을 하게 된 것이 상당히 기뻤습니다. 2년전 여름 일본에서 TUBE의 공연을 보고난 후 오랜만에 보게되는 공연이라 상당히 기대됩니다.

 무려 6개월 전부터 티켓을 예약하고 잊고 있던 와중에 집으로 티켓이 배송오더군요. 이제 이 티켓을 들고 공연이 열리는 고척스카이돔으로 향합니다.

고척스카이돔으로 가기 위해 구일역 2번 출구에서 내립니다.

응원도구를 판매하시는 분들도 Queen의 공연임을 알고 있었던걸까요?

구일역 2번 출구에서 내리면 입장하는 곳 까이 현수막으로 안내가 되어 있습니다.

공연장에 들어가기 전에 소지품을 맡겨야 합니다.

흔치않은 공연날이라 카메라를 들고 왔는데 공연장에 카메라를 들고 들어갈 수 없다는 걸 처음 알았습니다.
아직 공연을 잘 모르는 분들이라면 참고해주시길

걷다보니 저 멀리서 사람들이 길게 줄을 서 있었습니다.

자세히 보니 기념품 판매점 줄로 보입니다.
아침부터 줄을 서 있던 분이 계시다고 하시데군요.

일부 미판매 티켓의 경우 현장에서 예매가 가능합니다만
좌석은 매진되어 스탠딩 좌석만 구매가 가능하다고 하더군요.

우리나라에 이렇게 Queen의 현수막이 걸릴줄이야

입장 30분전. 아직까지는 여우가 있어 보입니다.

지정된 입구 이외의 입구는 출입이 통제되어 있습니다.

스탠딩의 경우 경기장 지하주차장에서 대기하게 됩니다.
겨울에는 상당히 춥다고 하더군요.

평소 돔구장이던 경기장이 오늘은 Queen의 공연장이 되었습니다.

공연장 인근에는 각종 행사들이 진행되고 있었습니다.

프레디와 브라이언을 흉내내는 분들 재치있습니다!

현대카드에서 지금까지 개최되었던 콘서트 리스트가 소개되어 있습니다.

입장이 시작되면 지정석 보유자들은 이 곳을 통해 입장하게 됩니다.

기념품점은 공연이 임박한 지금까지도 인산인해를 이루고 있습니다.

추운날 줄 서 계시느라 다들 고생이 많습니다.

입장이 시작될 때가 되어서인지 스탭들이 분주히 움직이고 있습니다.

공연장에는 생각보다 많은 물건들이 반입을 허용되지 않고 있습니다.
혹시 공연을 가시게 되는 분들은 꼼꼼히 살펴보시길

기둥쪽을 보니 로저, 애덤, 브라이언의 사진이 보이는군요.

공연장에 입장하기 전에 잠시 다리 건너 롯데마트에 들렀다 갑니다.

다리 건너에서 바라본 경기장의 모습
롯데마트에 카메라를 맡기고 다시 경기장으로 돌아갑니다.

지정석 입장이 시작될 대 즈음 스탠딩 관객들이 입장을 합니다.

제 자리는 209번 구역인데요 생각보다 무대가 잘 보였습니다.

스탠딩 관객분들께서 도착하자마자 바로 팬스 근처로 달려갑니다.

스태프들의 안내에 따라 관객들이 질서있게 들어옵니다.
중간에 불미스러운 일로 싸우시는 분들이 계시더군요.

반면 지정석은 자리가 정해져 있기 때문에 굳이 일찍 들어와 있을 필요는 없습니다.

무대의 중앙을 보니 스크린으로 왕관을 표현한 것 처럼 보이더군요.

무대가 정면이 아니면 공연을 관람하는 것이 힘들까봐 걱정했는데
어느 자리에서 보아도 무대가 잘 보입니다.

어느덧 무대 근처 팬스는 사람들이 금방 자리를 차지합니다.

공연이 시작되기 전 팬스 근처에 자리를 잡지 못한 분들은 바닥에 앉아서 기다리고 계십니다.

공연이 임박하자 스탠딩석에 사람들이 상당히 많이 모였습니다.
지정석도 자리가 거의 다 찼습니다.

어느덧 공연이 시작되고 사람들이 다같이 스마트폰을 들고 공연장을 촬영합니다.

폰카로는 공연을 찍는게 상당히 힘들더군요.
자세한 공연 내용은 아래 동영상을 보면 분위기를 알 수 있을 것입니다.



 공연은 최고였습니다. 돔구장의 시설을 적극적으로 활용하여 마치 우주에서 공연을 보는 듯한 기분이었습니다. 다음에 기회가 된다면 다시 한 번 공연을 관람하고 싶네요. 과연 퀸의 다음 공연은 어떤 모습일지 기대해봅니다.



Generalized Estimating Equations(일반화 추정 방정식)

공대생의 팁 2020. 1. 3. 23:42


 피험자 그룹에서 반복 측정(반응[Response] 및 공변량[Covariate])한 결과를 관찰한다고 가정해봅니다. 이러한 공변량을 기반으로 각각에 대한 예상 반응 모델링을 하고자 할 때 아래와 같은 몇 가지 경우가 있습니다.

  • ■ 몇 가지 통제된 식단 중 하나를 각 사람들 개별로 할당하고 시간이 지남에 따라 콜레스테롤 수치를 측정

  • ■ 시간의 경과에 따라 얻은 값과 어떤 변수의 관계 연구

  • ■ 아이를 갖는 것이 여성의 노동력 참여 가능성에 미치는 영향 결정

 위와 같이 (반복해서 측정하여 얻은) 패널 데이터가 갖는 장점은 시간적 차이가 없고 관찰할 수 없는 개인 간의 차이를 제어할 수 있다는 점입니다. 개인당 다중 관측치를 갖는 것은 개인 내 변화를 기초로 추정치를 산출할 수 있게 합니다.

 이러한 위의 경우들을 분석하는데 가장 쉬운 방법은 공변량이 결과에 적층 효과(Additive effect)를 미치는 데이터에 선형 모델을 적합화하는 것입니다. 변수가 선형 관계가 아닌 다른 것에 영향을 받는 경우(관심의 반응이 확률인 경우) 일반화 선형 모델(Generalized Linear Model)이 더 적합할 것입니다. GLM에는 다음과 같은 식이 있습니다.

$$Y_i = \mu_i + \varepsilon_i, \qquad g(\mu_i) = X_i'\beta$$

 여기서 개별 \(i\)의 경우 \(Y_i\)는 반응, \(X_i\)는 공변량, \(\beta\)는 계수의 벡터, \(\varepsilon_i\)는 임의의 오차, g는 가능한 반응 집합에서 공변량의 선형 함수에 사상되는 연결 함수(Link function) 입니다.

 매개변수를 추정하고 GLM을 추론하기 위해서는 오류가 독립적이고 동일하게 분포되어 있다고 가정해야 합니다. 패널 데이터의 경우, 각 개인에 대한 관찰은 상관관계가 존재하기 때문에 이는 명백히 사실이 아닙니다.

 한 가지 가능한 해결책은 모델 피팅에 피험자별 임의 효과(Random effect)를 포함하는 것입니다. 이 방법은 GLMM(Generalized Linear Mixed Model)이라고 부릅니다. GLMM은 모수적 가정(Parametric assumption)을 필요로 합니다.

 일반화 추정 방정식(Generalized Estimation Equation)은 이를 처리하기 위한 비모수적 방법(Nonparametric assumption)입니다. GEE의 아이디어는 모든 피험자에 대해 평균을 내고 대상 내 공분산 구조를 잘 예측하는 것입니다. 데이터가 특정 분포에서 생성되었다고 가정하는 대신 공변량과 반응 사이의 관계를 설명하기 위해 반복적으로 최선의 \(\beta\)를 선택하기 위해 모멘트 가정을 사용합니다.

 주의사항: GLMM과 GEE에 대한 결과물 해석은 다르다는 점을 유의 바랍니다.


피험자별 vs 전인원 평균

 GEE는 인구 평균 효과(Population average)를 추정합니다. 아래의 두 시나리오를 생각해봅니다.

● 시나리오1: 당신은 의사이고 스타틴 약이 당신의 환자가 심장마비에 걸릴 확률을 얼마나 낮출 수 있는지 알고 싶다.
● 시나리오2: 당신은 보건소 공무원이고 심장마비 위험에 처한 모든 사람들이 그 스테틴 약을 복용한다면 심장마비 사망자수를 줄일 수 있는지 알고싶다.

 첫 번째 시나리오에서는 각 개별 확률을 알자 합니다. 두 번째 시나리오는 전체 인구에 대한 예측에 관심을 두고 있습니다. GEE는 두 번째 시나리오에서 추정할 수 있지만 첫 번째 시나리오에서는 알 수 없습니다.

GEE의 기초

 GEE는 인구 평균 모델 매개변수와 이들의 표준 오류를 추정합니다. GEE에 대한 가정은 GLM에 대한 가정과 비슷합니다.

  • 1. 응답 \(Y_1, Y_2, ... , Y_n\)은 상관관계가 있거나 무리를 이룬다.
  • 2. 연결함수 g에 의해 설명된 공변량과 반응의 변환 사이에는 선형 관계가 있다.
  • 3. 피험자 내의 공분산에는 몇 가지 구조("working 공분산")가 있다.
  • ● 독립성(시간 경과에 따른 관찰은 독립적)
  • ● 교환가능(시간 경과에 따른 모든 관측치에는 동일한 상관 관계가 있음)
  • ● 비정형(모든 시점 간의 상관관계는 다를 수 있음)

 GEE에 맞추기 위해 이러한 working 공분산 구조 중 하나를 선택해야 합니다. GLM과 마찬가지로 GEE는 반복적으로 최소 가중치를 조정하여 가중치로 작용하는 공분산 행렬을 연결합니다. 가중 최소 제곱 문제는 등식 추정식(Eponymous estimating equation)입니다. 만약 최대 가능성(Maximum likelihood)에 익숙하다면, 이 방정식을 Score function(log-likelihood의 1차 미분값)이라고 생각할 수 있습니다. 이 함수는 \(\beta\)의 최적 선택시 0과 같습니다.

 데이터 생성 과정(선형성)에 어떤 구조를 적용하더라도 분포를 완전히 특정하지 않습니다. \(\beta\)추정은 순전히 최적화의 연습입니다.


공분산이 잘못 정의되어 있는 것이 걱정된다면?

 β를 추정하기 위해서는 공분산 구조를 선택해야 하지만, 만약 β가 올바르게 나타나지 않는다면 어떻게 해야할까요?

 추정 방정식은 실제로 첫 번째 경우를 기준으로 하기 때문에 \(\beta\)는 Working 공분산 구조가 잘못되었다 하더라도 일관되게 추정될 수 있을것입니다. 그러나, 이로부터 계산된 표준 오차는 잘못될 것입니다. 이 문제를 해결하기 위해서는 견고성(Robustness)을 위해Huber-White의 "샌드위치 추정기(Sandwich estimator)"를 사용하여 GEE를 사용합니다. 샌드위치 분산 추정기의 기본 개념은 아이디어는 경험적 공분산을 사용하여 기본 공분산에 근사화하는 것입니다.

 그렇다면 왜 Working 공분산을 지정해야할까요?

1. 통계 효율(Statistical efficiency)
2. 샌드위치 견고성(Sandwich robustness)는 표본이 큰 특성값

  그렇다면 항상 샌드위치 추정기를 사용해야만 할까요?

 아닙니다. 만약 아래와 같은 경우가 생길 경우 그렇지 않습니다.

1. 독립된 피험자의 수가 반복되는 측정치의 수보다 훨씬 적을때
2. 설계의 균형이 맞지 않을때(반복되는 측정치의 수가 개별로 다른 경우)

장점

● 최대우도측정(Maximum Likelihood Estimation)에 비해 계산이 단순합니다.
● 분포에 대한 가정이 없습니다.
● 상관 구조가 잘못 정의되어 있더라도 추정치는 일치합니다.(평균 응답에 대한 모델이 올바르다고 가정하였을 때)

한계

● 우도 기반 방법(Likelihood-based method)은 통상적인 통계적 추론에 사용할 수 없습니다. GEE는 준우도 방법(Quasi-likelihood method)입니다.
● GEE는 단지 추정 절차일 뿐이므로 모델 선택을 수행하는 방법이 명확하지 않습니다. 적합도 측정은 쉽게 구할수가 없습니다.
● 피험체에 특정된 측정을 할 수 없습니다.

GEE의 확장

● GEE2: 2차 연장
    ○ 여기서 소개한 GEE의 버전은 GEE1입니다.
    ○ 아이디어: 공분산을 연구하기 위한 더 복잡한 방정식을 사용합니다.

● 대체 로지스틱 회귀(Alternating Logistic Regression) (Carey, Zeger, and Diggle(1993)): 다른 조건으로 결과 모델링
    ○ 아이디어: 모델 연결에 상관관계 대신 로그 Odd ratio를 사용합니다.

GEE에 대한 심화학습

● 첫 번째 평균공분산(준우도 접근법)
샌드위치 추정기(Sandwich estimator)를 사용하여 공분산의 설정 오류(misspecification) 방지
인구 평균 효과(Population-averaged effects) 모델링
● 대상 내 의존성이 관찰되지 않거나 알려지지 않은 경우 유용성
● 여전히 대상 독립성을 가정(공변량에 따라 조건화됨)


참고자료: https://rlbarter.github.io/Practical-Statistics/2017/05/10/generalized-estimating-equations-gee/

아름다운 영일만에 기적이 울리다 - 영일만항선[2019.12.18]


 우리나라 물류의 주요 수송방법이 육로가 대부분이라고 하지만 철도를 이용한 수송이 육로보다 효율적이기에 지금까지도 철도를 통한 물류 운송이 활발하게 이어지고 있습니다. 그럼에도 춘장대역으로 대표되는 서천화력선과 같이 더이상 사용되지 않고 폐선되는 철로가 있기도 합니다.

 지난 2019년 12월 18일 화물철도인 영일만항선이 정식으로 개통되었습니다. 이번 포스팅에서는 새로 개통된 영일만항역 인근의 풍경을 담아보았습니다.



 지도를 보았을 때 영일만은 포항시에 둘러싸인 모습을 하고 있습니다. 호랑이의 꼬리 부분에 해당하는 호미곶이 영일만을 감싸는 듯한 모습을 하고 있군요.




 영일만항선은 포항역에서부터 영일만항까지 연결되어 있습니다. 영일만항 바로 옆에는 용한1리 해수욕장이 위치해 있습니다.



포항역에 내리다가 본 내일로 포스터

2019년 겨울 내일로는 만 34살까지 이용할 수 있습니다!



포항역에서 차로 15분 거리에 있는 포항국제컨테이너터미널에 도착하였습니다.



터미널 근처는 작은 어선들이 있습니다.



오늘 영일만항선의 개통식을 알리는 현수막들이 걸려있습니다.



최근 입체화 과정에서 사라져가는 건널목이 새로 만들어져있습니다.



아무래도 화물 열차가 많이 다니지 않아서인지 굳이 입체화를 할 필요가 없었던 듯 합니다.



희안하게 이 건널목에는 삼색 신호등도 설치되어 있습니다.

횡단보도가 없는 것으로 보다 기차가 지나갈 때만 동작하는 신호등인듯 합니다.



인근에 건널목이 있다는 표시판



철로는 컨테이너 터미널 내부로 연결되어 있습니다.



영일항만 주변은 공터가 대부분이었습니다.



곧 열차가 지나가려는지 청색 표시등이 켜져있습니다.



내부에서 개통식 행사를 하는 과정에서 시운전이 있었는지 멀리서 기관차가 보입니다.

다만 철문이 막혀있어 당장 건널목을 건널 수 없는 상황입니다.


이번에는 영일항만 근처 해수욕장을 찾아가 보았습니다.





해수욕장에 도착하자마자 서핑을 즐기는 사람들을 보았습니다.

이 추운 겨울에도 서핑을 하고자 하는 열정 대단합니다!



영일항만에 가려져서 이렇게 훌륭한 붕경을 보여주는 해수욕장이 근처에 있었다는 것이 신기하네요.



역시 동해안 답게 푸르른 바닷물이 인상깊습니다.



근처에 주차된 차량들이 상당히 많아 보이는데 무슨 일로 이 곳에 찾아온걸까요?



해수욕장에서 부터 방파제를 따라 만들어진 길을 산책해봅니다.



계속 걷다보니 아까 그 많은 사람들이 어디에 갔는지 알게 되었습니다.



어부가 잡은 물고기들이 바닥에서 싱싱하게 파닥거리고 있었습니다.



영일만 주변에 만들어진 방파제를 따라 사람들이 낚시를 즐기고 있었습니다!



등대 주변을 가보니 낚시를 즐기시는 분들이 많이 보입니다.



동해답게 물이 상당히 맑았습니다.

사진으로 다시 보니 실물보다 잘 안나오네요.



추운 날씨에 월척 한 마리 꼭 낚으시길!



다시 육지로 돌아가는길

생각보다 상당히 긴 길이었습니다.



저도 언젠가는 날씨 좋은 날에 바다낚시를 한 번 즐겨보고 싶네요!



다시 해수욕장에 돌아오니 서핑을 즐기시던 분들이 바다 바깥으로 나와계십니다.

가만히 서있기만 해도 추워보이는데 말이지요.



해수욕장과 영일만항을 함께 바라본 모습.



길 옆에 바닷가가 확 트인 모습 저는 매우 좋아합니다.



다시 포항역에 돌아가기 전 차 안에서 이 멋진 광경을 바라보며 영일만을 떠나갑니다... 



이 장소를 Daum지도에서 확인해보세요.
경북 포항시 북구 흥해읍 용한리 853 | 포항영일만항
도움말 Daum 지도

동영상으로 보는 엔코더의 작동 원리

임베디드 2019. 12. 16. 00:43


 이전에 작성하였던 엔코더의 작동 원리에 대한 글이 지금까지도 많은 분들께서 찾아와주시는 것을 보면서 제 글이 많은 사람들에게 도움을 드리고 있다는 것에 상당한 뿌듯함을 느꼇습니다. 지금도 댓글로 질문을 달아주시는 분들이 계시는 것을 보면서 많은 분들께서 엔코더에 대해 궁금해 하시는 것이 많다는 것을 알게 되었습니다.


 어떻게 하면 엔코더의 작동 과정을 직관적으로 설명할 수 있을까 하다가 유튜브에서 좋은 영상 자료를 구하게 되어 여러분들께 보여드리고자 합니다. 먼저 동영상을 보신 다음 아래의 설명을 읽어주신다면 이해하는데 많은 도움이 되시리라 믿습니다.


Absolute(절대)방식과 Incremental(증분)방식 엔코더의 차이점





 엔코더의 구조로 크게 선형(Linear)엔코더와 로터리(Rotary)엔코더가 있습니다. 선형엔코더는 이름 그대로 직선으로 이동하며 위치를 측정하는 용도로 사용되며 로터리엔코더는 회전 정보를 측정하는 용도로 사용됩니다.



 선형엔코더는 트랜스듀서(Transducer)를 사용하여 위치 변화를 측정합니다. 로터리(샤프트)엔코더의 경우 샤프트 축을 기준으로 회전 과정에서 얻게 되는 정보를 측정합니다. 위의 로터리엔코더는 Absolute 방식으로 회전 각도에 대한 정보를 출력합니다.



 그렇다면 로터리엔코더의 Absolute 방식과 Incremental 방식은 어떠한 차이가 있을까요? 두 엔코더는 사용 목적에 따라 크게 다릅니다. 측정하고자 하는 대상이 샤프트를 중심으로 어느 정도 회전했는지를 360도 이내에서 알아내기 위해서는 Absolute 방식이 적합하고, 회전 속도를 측정하는 것이 목적이라면 Incremental 방식이 적합합니다.

 Absolute 방식은 어느 정도 회전 했는지의 각도 정보(Angular Position)을 제공하며 Incremental 방식은 엔코더의 회전횟수에 따른 물체의 속도와 이동거리를 측정할 수 있습니다.



 Incremental 방식의 엔코더의 회전 속도를 측정하는 방법은 위의 그림과 같이 엔코더 내부의 슬릿(틈새)사이로 빛을 쏘는 부분과 빛은 수신하는 부분이 맞물린 구조에서 이루어집니다. 엔코더가 회전할 때 슬릿이 지나갈 때 마다 수신하는 부분에서는 빛이 차단되다 다시 수신되는 과정을 거치게 됩니다.



 Absolute 방식의 엔코더는 특정한 각도만큼 회전하였을 때 출력값이 위에서 보는 바와 같이 다르게 나타납니다. 이는 엔코더의 광수신 부분 갯수에 따라 각을 나타낼 수 있는 값이 더 미세해집니다. 위의 예제의 경우 3개의 광수신기가 있으므로 3^2=8개의 위치를 파악할 수 있는 것입니다.



 Absolute 엔코더는 구조에 따라 싱글턴(Single-turn) 방식과 멀티턴(Multi-turn) 방식이 있습니다. 싱글턴 방식의 경우 엔코더가 회전할 수 있는 360º 범위를 측정할 수 있습니다. 쉽게 말해 Absolute 엔코더가 15º 만큼 돌았는지 375º 만큼 한바퀴를 더 돌았는지 아닌지를 알 수 없다는 것입니다.

 이러한 싱글턴 엔코더의 한계를 해결하고자 한 것이 멀티턴 엔코더입니다. Absolute 엔코더의 광수신 부분을 위의 그림과 같이 하나 더 설치하여 엔코더의 회전수를 체크할 수 있도록 하여 각도의 변화량 측정 범위를 360º 이상의 범위에서 측정할 수 있게 됩니다.



 Incremental 엔코더의 경우 회전할 때 마다 광수신부의 빛이 차단과 수신을 반복하게 되면서 출력으로 Rising edge trigger가 수신될 때 마다 값을 1씩 증가시켜 엔코더의 회전 여부 및 속도를 나타낼 수 있습니다.


 그렇다면 엔코더의 회전 방향이 샤프트를 기준으로 시계방향(CW)인지 시계반대방향(CCW)인지 확인할 수 있는 방법은 어떤 것이 있을까요? 아래의 동영상을 통해 이해해보도록 합니다.


Incremental 방식 엔코더의 A상과 B상





 위에서 설명드렸듯이 Incremental 엔코더가 회전할 때 counting disc를 지날 때 마다 카운터가 1씩 증가되는 과정을 위 동영상에서 다시 확인하실 수 있습니다.



 이러한 특성의 Incremental 엔코더에 counting disc를 두 개로 만들어 두 개의 상이 90º 차이가 나도록 설계합니다. 이렇게 되면 A상과 B상이 서로 90º 위상 차이로 출력되고 있는 것을 보실 수 있습니다.



 지속적으로 회전을 해보면 A상이 90º 차이로 B상보다 먼저 출력되고 있음을 확인하실 수 있습니다.



 반대로 이번에는 엔코더의 회전 방향을 반대로 돌려봅니다. 이 경우 B상이 A상보다 90º 빠르게 돌아가고 있음을 확인하실 수 있습니다. 이러한 두 상의 위상차를 통해 엔코더의 회전 방향을 파악할 수 있음을 알 수 있습니다.


 엔코더의 작동 원리에 대해 좀 더 자세히 알아보고자 하시는 분들은 이전에 정리하였던 제 글을 참조해 주셨으면 합니다!


2016/07/31 - [임베디드] - [로터리 엔코더] 엔코더의 작동 원리 및 사용 방법


안드로이드 기기 간에 Wifi Socket 통신하기(Kotlin)


 오랜만에 안드로이드를 다루어보는 기회가 생겼는데 생각보다 많은 변화가 있었습니다. 특히 올해부터는 구글에서 새로 만든 프로그래밍 언어인 Kotlin이 도입되면서 Java 위주로 설계된 안드로이드 생태계에 거대한 변화가 있을 것으로 보입니다. 이러한 안드로이드의 환경 변화에 맞추어 제가 기존에 다루었던 프로그램들을 Kotlin으로 다시 한 번 설계해보려고 합니다.


 이전에 안드로이드 기기간의 Wi-Fi 통신 프로그래밍을 설계하였던 적이 있었는데 Kotlin으로 다시 짜보는 김에 프로그램을 최적화 해서 다시 만들어보았습니다.


AndroidMenifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.elecs.interandroidcommunication">
 
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
</manifest>
cs


strings.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resources>
    <string name="app_name">IAC</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="ip">IP</string>
    <string name="port">PORT</string>
    <string name="name">NAME</string>
    <string name="msg">MESSAGE</string>
    <string name="hint_port">PORT NUMBER</string>
    <string name="hint_ip">192.168.0.1</string>
    <string name="hint_msg">Hello, world!</string>
    <string name="button1">Connect</string>
    <string name="button2">Disconnect</string>
    <string name="button3">Set Server</string>
    <string name="button4">Close Server</string>
    <string name="button5">View my info</string>
    <string name="button6">Send Message</string>
</resources>
 
cs


activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <TextView
                android:id="@+id/textView"
                android:layout_width="70dp"
                android:layout_height="wrap_content"
                android:text="@string/ip" />
 
            <EditText
                android:id="@+id/et_ip"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="@string/hint_ip"
                android:importantForAccessibility="no"
                tools:ignore="Autofill"
                android:inputType="textUri"
                android:singleLine="true" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <TextView
                android:id="@+id/textView2"
                android:layout_width="70dp"
                android:layout_height="wrap_content"
                android:text="@string/port" />
 
            <EditText
                android:id="@+id/et_port"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="@string/hint_port"
                android:importantForAccessibility="no"
                tools:ignore="Autofill"
                android:inputType="number"
                android:singleLine="true" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <TextView
                android:id="@+id/textView3"
                android:layout_width="70dp"
                android:layout_height="wrap_content"
                android:text="@string/msg" />
 
            <EditText
                android:id="@+id/et_msg"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="@string/hello_world"
                android:importantForAccessibility="no"
                tools:ignore="Autofill"
                android:inputType="textPersonName" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <Button
                android:id="@+id/button_connect"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:minWidth="0dip"
                android:text="@string/button1"
                android:textSize="12sp" />
 
            <Button
                android:id="@+id/button_disconnect"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:minWidth="0dip"
                android:text="@string/button2"
                android:textSize="12sp" />
 
            <Button
                android:id="@+id/button_msg"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:minWidth="0dip"
                android:text="@string/button6"
                android:textSize="12sp" />
        </LinearLayout>
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
 
            <Button
                android:id="@+id/button_setserver"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/button3"
                android:textSize="12sp" />
 
            <Button
                android:id="@+id/button_closeserver"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/button4"
                android:textSize="12sp" />
 
            <Button
                android:id="@+id/button_info"
                style="@style/Widget.AppCompat.Button.Small"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/button5"
                android:textSize="12sp" />
 
        </LinearLayout>
 
        <TextView
            android:id="@+id/text_status"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
 
    </LinearLayout>
 
</androidx.constraintlayout.widget.ConstraintLayout>
cs


MainActivity.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package com.elecs.interandroidcommunication
 
import android.content.Context
import android.net.ConnectivityManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.io.DataInputStream
import java.io.DataOutputStream
import java.net.*
 
class MainActivity : AppCompatActivity() {
 
    companion object{
        var socket = Socket()
        var server = ServerSocket()
        lateinit var writeSocket: DataOutputStream
        lateinit var readSocket: DataInputStream
        lateinit var cManager: ConnectivityManager
 
        var ip = "192.168.0.1"
        var port = 2222
        var mHandler = Handler()
        var closed = false
    }
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        cManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        server.close()
        socket.close()
 
        button_connect.setOnClickListener {    //클라이언트 -> 서버 접속
            if(et_ip.text.isNotEmpty()) {
                ip = et_ip.text.toString()
                if(et_port.text.isNotEmpty()) {
                    port = et_port.text.toString().toInt()
                    if(port<0 || port>65535){
                        Toast.makeText(this@MainActivity, "PORT 번호는 0부터 65535까지만 가능합니다.", Toast.LENGTH_SHORT).show()
                    }else{
                        if(!socket.isClosed){
                            Toast.makeText(this@MainActivity, ip + "에 이미 연결되어 있습니다.", Toast.LENGTH_SHORT).show()
                        }else {
                            Connect().start()
                        }
                    }
 
                }else{
                    Toast.makeText(this@MainActivity, "PORT 번호를 입력해주세요.", Toast.LENGTH_SHORT).show()
                }
            }else{
                Toast.makeText(this@MainActivity, "IP 주소를 입력해주세요.", Toast.LENGTH_SHORT).show()
            }
        }
 
        button_disconnect.setOnClickListener {    //클라이언트 -> 서버 접속 끊기
            if(!socket.isClosed){
                Disconnect().start()
            }else{
                Toast.makeText(this@MainActivity, "서버와 연결이 되어있지 않습니다.", Toast.LENGTH_SHORT).show()
            }
        }
 
        button_setserver.setOnClickListener{    //서버 포트 열기
            if(et_port.text.isNotEmpty()) {
                val cport = et_port.text.toString().toInt()
                if(cport<0 || cport>65535){
                    Toast.makeText(this@MainActivity, "PORT 번호는 0부터 65535까지만 가능합니다.", Toast.LENGTH_SHORT).show()
                }else{
                    if(server.isClosed) {
                        port = cport
                        SetServer().start()
                    }else{
                        val tstr = port.toString() + "번 포트가 열려있습니다."
                        Toast.makeText(this@MainActivity, tstr, Toast.LENGTH_SHORT).show()
                    }
                }
            }else{
                Toast.makeText(this@MainActivity, "PORT 번호를 입력해주세요.", Toast.LENGTH_SHORT).show()
            }
        }
 
        button_closeserver.setOnClickListener {    //서버 포트 닫기
            if(!server.isClosed){
                CloseServer().start()
            }else{
                mHandler.obtainMessage(17).apply {
                    sendToTarget()
                }
            }
        }
 
        button_info.setOnClickListener {    //자기자신의 연결 정보(IP 주소)확인
            ShowInfo().start()
        }
 
        button_msg.setOnClickListener {    //상대에게 메시지 전송
            if(socket.isClosed){
                Toast.makeText(this@MainActivity, "연결이 되어있지 않습니다.", Toast.LENGTH_SHORT).show()
            }else {
                val mThread = SendMessage()
                mThread.setMsg(et_msg.text.toString())
                mThread.start()
            }
        }
 
        mHandler = object : Handler(Looper.getMainLooper()){  //Thread들로부터 Handler를 통해 메시지를 수신
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                when(msg.what){
                    1->Toast.makeText(this@MainActivity, "IP 주소가 잘못되었거나 서버의 포트가 개방되지 않았습니다.", Toast.LENGTH_SHORT).show()
                    2->Toast.makeText(this@MainActivity, "서버 포트 "+port +"가 준비되었습니다.", Toast.LENGTH_SHORT).show()
                    3->Toast.makeText(this@MainActivity, msg.obj.toString(), Toast.LENGTH_SHORT).show()
                    4->Toast.makeText(this@MainActivity, "연결이 종료되었습니다.", Toast.LENGTH_SHORT).show()
                    5->Toast.makeText(this@MainActivity, "이미 사용중인 포트입니다.", Toast.LENGTH_SHORT).show()
                    6->Toast.makeText(this@MainActivity, "서버 준비에 실패하였습니다.", Toast.LENGTH_SHORT).show()
                    7->Toast.makeText(this@MainActivity, "서버가 종료되었습니다.", Toast.LENGTH_SHORT).show()
                    8->Toast.makeText(this@MainActivity, "서버가 정상적으로 닫히는데 실패하였습니다.", Toast.LENGTH_SHORT).show()
                    9-> text_status.text = msg.obj as String
                    11->Toast.makeText(this@MainActivity, "서버에 접속하였습니다.", Toast.LENGTH_SHORT).show()
                    12->Toast.makeText(this@MainActivity, "메시지 전송에 실패하였습니다.", Toast.LENGTH_SHORT).show()
                    13->Toast.makeText(this@MainActivity, "클라이언트와 연결되었습니다.",Toast.LENGTH_SHORT).show()
                    14->Toast.makeText(this@MainActivity,"서버에서 응답이 없습니다.", Toast.LENGTH_SHORT).show()
                    15->Toast.makeText(this@MainActivity, "서버와의 연결을 종료합니다.", Toast.LENGTH_SHORT).show()
                    16->Toast.makeText(this@MainActivity, "클라이언트와의 연결을 종료합니다.", Toast.LENGTH_SHORT).show()
                    17->Toast.makeText(this@MainActivity, "포트가 이미 닫혀있습니다.", Toast.LENGTH_SHORT).show()
                    18->Toast.makeText(this@MainActivity, "서버와의 연결이 끊어졌습니다.", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
 
    class Connect:Thread(){
 
        override fun run(){
            try{
                socket = Socket(ip, port)
                writeSocket = DataOutputStream(socket.getOutputStream())
                readSocket = DataInputStream(socket.getInputStream())
                val b = readSocket.read()
                if(b==1){    //서버로부터 접속이 확인되었을 때
                    mHandler.obtainMessage(11).apply {
                        sendToTarget()
                    }
                    ClientSocket().start()
                }else{    //서버 접속에 성공하였으나 서버가 응답을 하지 않았을 때
                    mHandler.obtainMessage(14).apply {
                        sendToTarget()
                    }
                    socket.close()
                }
            }catch(e:Exception){    //연결 실패
                val state = 1
                mHandler.obtainMessage(state).apply {
                    sendToTarget()
                }
                socket.close()
            }
 
        }
    }
 
    class ClientSocket:Thread(){
        override fun run() {
            try{
                while (true) {
                    val ac = readSocket.read()
                    if(ac == 2) {    //서버로부터 메시지 수신 명령을 받았을 때
                        val bac = readSocket.readUTF()
                        val input = bac.toString()
                        val recvInput = input.trim()
 
                        val msg = mHandler.obtainMessage()
                        msg.what = 3
                        msg.obj = recvInput
                        mHandler.sendMessage(msg)
                    }else if(ac == 10){    //서버로부터 접속 종료 명령을 받았을 때
                        mHandler.obtainMessage(18).apply {
                            sendToTarget()
                        }
                        socket.close()
                        break
                    }
                }
            }catch(e:SocketException){    //소켓이 닫혔을 때
                mHandler.obtainMessage(15).apply {
                    sendToTarget()
                }
            }
        }
    }
 
    class Disconnect:Thread(){
        override fun run() {
            try{
                writeSocket.write(10)    //서버에게 접속 종료 명령 전송
                socket.close()
            }catch(e:Exception){
 
            }
        }
    }
 
    class SetServer:Thread(){
 
        override fun run(){
            try{
                server = ServerSocket(port)    //포트 개방
                mHandler.obtainMessage(2"").apply {
                    sendToTarget()
                }
 
                while(true) {
                    socket = server.accept()
                    writeSocket = DataOutputStream(socket.getOutputStream())
                    readSocket = DataInputStream(socket.getInputStream())
 
                    writeSocket.write(1)    //클라이언트에게 서버의 소켓 생성을 알림
                    mHandler.obtainMessage(13).apply {
                        sendToTarget()
                    }
                    while (true) {
                        val ac = readSocket.read()
                        if(ac==10){    //클라이언트로부터 소켓 종료 명령 수신
                            mHandler.obtainMessage(16).apply {
                                sendToTarget()
                            }
                            break
                        }else if(ac == 2){    //클라이언트로부터 메시지 전송 명령 수신
                            val bac = readSocket.readUTF()
                            val input = bac.toString()
                            val recvInput = input.trim()
 
                            val msg = mHandler.obtainMessage()
                            msg.what = 3
                            msg.obj = recvInput
                            mHandler.sendMessage(msg)    //핸들러에게 클라이언트로 전달받은 메시지 전송
                        }
                    }
                }
 
            }catch(e:BindException) {    //이미 개방된 포트를 개방하려 시도하였을때
                mHandler.obtainMessage(5).apply {
                    sendToTarget()
                }
            }catch(e:SocketException){    //소켓이 닫혔을 때
                mHandler.obtainMessage(7).apply {
                    sendToTarget()
                }
            }
            catch(e:Exception){
                if(!closed) {
                    mHandler.obtainMessage(6).apply {
                        sendToTarget()
                    }
                }else{
                    closed = false
                }
            }
        }
    }
 
    class CloseServer:Thread(){
        override fun run(){
            try{
                closed = true
                writeSocket.write(10)    //클라이언트에게 서버가 종료되었음을 알림
                socket.close()
                server.close()
            }catch(e:Exception){
                e.printStackTrace()
                mHandler.obtainMessage(8).apply {
                    sendToTarget()
                }
            }
        }
    }
 
    class SendMessage:Thread(){
        private lateinit var msg:String
 
        fun setMsg(m:String){
            msg = m
        }
 
        override fun run() {
            try{
                writeSocket.writeInt(2)    //메시지 전송 명령 전송
                writeSocket.writeUTF(msg)    //메시지 내용 
            }catch(e:Exception){
                e.printStackTrace()
                mHandler.obtainMessage(12).apply {
                    sendToTarget()
                }
            }
        }
    }
 
    class ShowInfo:Thread(){
 
        override fun run(){
            lateinit var ip:String
            var breakLoop = false
            val en = NetworkInterface.getNetworkInterfaces()
            while(en.hasMoreElements()){
                val intf = en.nextElement()
                val enumIpAddr = intf.inetAddresses
                while(enumIpAddr.hasMoreElements()){
                    val inetAddress = enumIpAddr.nextElement()
                    if(!inetAddress.isLoopbackAddress && inetAddress is Inet4Address){
                        ip = inetAddress.hostAddress.toString()
                        breakLoop = true
                        break
                    }
                }
                if(breakLoop){
                    break
                }
            }
 
            val msg = mHandler.obtainMessage()
            msg.what = 9
            msg.obj = ip
            mHandler.sendMessage(msg)
        }
    }
 
}
 
cs



위의 코드를 실행화면 다음과 같은 화면이 나타납니다.




※Sever 사용 방법

1. Port에 원하는 포트 번호를 입력합니다.

2. 'SET SERVER' 버튼을 클릭하면 포트를 열 수 있습니다. 만약 다른 프로세스에서 포트를 사용중일 경우 다른 포트 번호를 입력합니다.

3. 'VIEW MY INFO' 버튼을 클릭하면 현재 접속중인 IP주소를 알 수 있습니다. 이를 Client의 IP란에 입력해주세요.

4. Client와의 연결이 성공하면 Toast로 연결에 성공하였다는 알림이 나옵니다.


※Client 사용 방법

1. IP와 PORT에 서버의 IP주소와 설정 PORT 번호를 입력합니다.

2. 'CONNECT' 버튼을 클릭하여 서버에 접속합니다. 성공시 Toast로 접속에 성공하였다는 메시지를 확인하실 수 있습니다.

3. 'SEND MESSAGE' 버튼을 클릭하면 MESSAGE 칸에 입력한 텍스트 정보를 상대에게 전송할 수 있습니다.

4.  'DISCONNECT' 버튼을 눌러 서버와의 연결을 해제합니다.

Surrogate model(대체 모델)

공대생의 팁 2019. 11. 6. 12:56


 Surrogate model(대체 모델, 근사수학모델)이란 자동차 충돌 실험과 같이 제한된 계산 비용이 많이 드는 시뮬레이션을 기반으로 복잡한 시스템의 수많은 입출력 특성을 실제 모형과 유사하게 만드는 것을 목적으로 하는 소형 확장 분석 모델을 일컫는 말입니다.


 Surrogate model은 시뮬레이션 모델의 복잡한 동작을 흉내낼 수 있으며, 이러한 특성은 설계 자동화, 매개변수 분석, 우주 탐사에 관한 설계, 최적화 및 민감도 분석등에 사용될 수 있습니다.


 Surrogate model은 또한 meta model(메타 모델), response surface model(RSM, 반응표면분석법), 에뮬레이터, auxiliary model(보조 모델), repro-model(복제 모델)이라고도 부릅니다.


https://www.esteco.com/modefrontier/method-selecting-surrogate-models-crashworthiness-optimization


 교통사고와 같이 의도치 않은 상황에서 운전자가 생존할 수 있는 방법을 연구하기 위해서는 차량의 충돌을 분석하여 이를 통해 운전자의 생존률을 높이는 방법을 찾는 것은 매우 중요합니다. 그러나 차량 한 대의 가격은 매우 비싸기 때문에 수많은 차량들을 이용하여 충돌 실험을 하게 될 경우 엄청난 양의 비용이 필요합니다. 만약 Surrogate model을 설계하여 이를 활용한다면 해당 모델로 만들어진 차량을 시뮬레이션으로 반복적으로 사용할 수 있게 되고 실제 차량을 사용하여 발생하는 비용을 최소화 할 수 있습니다. 또한, surrogate model을 사용함으로서 얻게 되는 최적화된 솔루션을 개발하여 실제 차량에 적용한다면 더 좋은 결과를 얻을 수 있을 것입니다.


 

https://www.oreilly.com/radar/ideas-on-interpreting-machine-learning/


 인공지능을 해석함에 있어 surrogate model은 매우 중요한 모델입니다. 2019년 현재 시점에서 인공신경망을 해석하기에는 매우 어렵습니다. 너무나도 많은 매개변수(Parameter)들이 있고 이들의 변화를 사람의 눈으로 파악하기엔 변수가 너무나 많기 때문입니다.

 이러한 인공신경망 모델을 해석하기 위해 입력값을 의사결정트리 혹은 선형모델에 대입하여 surrogate model로 만들어 해당 인공신경망 모델의 특성을 최대한 이해하는 용도로 사용할 수 있습니다.

 



참고자료: http://sumo.intec.ugent.be/surrogates

VirtualBox에 안드로이드 설치 도중 부팅이 멈출 때 해결방법

공대생의 팁 2019. 10. 21. 14:42


 한동안 안드로이드와는 거리를 두고 지내던 도중 종종 관련 질문이 들어와서 최신 버전인 Oreo를 VirtualBox에 설치를 아래와 같은 방식으로 시도해 보았었습니다.


안드로이드 버추얼박스(VirtualBox)에 설치하기


 제가 안드로이드를 설치하려 하였던 환경은 아래와 같습니다.


Host OS버전: Ubuntu 16.04

VirtualBox 버전: 6.0.14

Android 버전: 8.1(Oreo)


 위와 같은 환경에서 링크에서 제공하는 설치 방법대롤 하였으나 아래와 같이 화면이 멈추면서 더이상 진행이 되지 않고 있었습니다.


 Trusted GRUB now loading 'Android-x86 8.1-r2'

 Progress: Detecting Android-x86... found at /dev/sda1

X86_64:/ #



정황상 안드로이드가 설치된 파티션을 찾아내어 이를 불러오는 것 까지는 성공한 것 같으나 현 상황에서 알 수 없는 이유로 화면이 더이상 나올 수 없는 상황으로 추정됩니다.


 이를 해결하기 위해 VirutlaBox에서 설정→디스플레이로 접근하신 다음 아래와 같이 나왔을 때 'VBoxVGA'를 선택해줍니다.



 위에 표시도니 VBoxVGA로 설정하신 후 다시 안드로이드를 실행해보시면 아래와 같이 부팅이 정상적으로 동작하고 있는 것을 확인하실 수 있습니다!



Canon MG2990 시리즈 USB 연결 설치방법

흔치않은일상 2019. 10. 11. 02:03


 이사를 하면서 통신사에서 제공하는 공유기를 사용하게 되었는데 호환이 잘 안되는 것 같아 USB로 연결하여 설치해 보았습니다. 이번에 공유기를 통해 무선으로 설치하는 방법을 다루어 본 적이 있었는데 혹시 무선 설치가 안되시는 분들을 위해 이렇게 USB 유선 연결 설치 방법도 소개해 드려볼까 합니다.


 혹시 공유기가 프린터 근처에 있고 무선으로 여러 대의 기기를 사용하고 계신 분이라면 제가 이전에 작성하였던 무선 설치 방법을 참고해 주시기 바랍니다.


    Canon MG2900 시리즈 무선 프린터 설치방법



※설치하시기 전에 반드시 프린터와 컴퓨터와 USB를 빼주세요.


1. 아래의 사이트에 접속하여 '드라이버 통합 설치파일'을 다운로드 받습니다.(Windows 기준)

http://www.canon-bs.co.kr/person/download.aspx?no=3588&category1=2



2. 다운로드 받은 파일을 실행하면 아래와 같은 화면이 나옵니다.



3. '다음' 버튼을 누르면 아래와 같이 드라이버 설치 준비를 합니다.



4. 프린터 연결 방법을 선택합니다. 여기서는 'USB 연결'을 선택해 보겠습니다.



5. 사용자의 거주지를 선택합니다.



6. 자신이 거주하는 국가를 선택해줍니다.



7. 맨 위에 있는 'MP Drivers'를 설치해줍니다.(기본으로 선택이 되어있음)



8. '예' 버튼을 클릭해주세요.



9. '예' 버튼을 클릭해 주세요.



10. 인터넷을 통해 설치 파일을 받는 과정입니다. 잠시 기다려줍니다.



11. 다음과 같은 화면이 나오면 이제 컴퓨터와 프린터를 USB로 연결해줍니다.



 MG2990 프린터의 경우 USB 꽃는 곳은 다음과 같이 뒷부분 오른쪽에 위치해 있습니다.

 프린터에 연결되는 USB는 TypeB 형식으로 일반적인 프린터에서 자주 사용하는 방식입니다.



12. 프린터를 연결하고 프린터의 전원을 키면 아래와 같이 컴퓨터와 프린터가 연결되었음을 확인하실 수 있습니다.



13. 기다리시면 컴퓨터에 드라이버가 자동으로 설정이 됩니다.



14. 프린트 헤드 정력 과정입니다. 만약 프린터를 처음 사용하는 경우라면 실행 버튼을 눌러 안내가 나오는 대로 진행하시면 됩니다.



15. '다음' 버튼을 누르시면 프린터의 설치가 완료됩니다! 이제 프린터를 자유롭게 사용하실 수 있게 되었습니다!



16. '끝내기' 버튼을 눌러 설치 프로그램을 종료합니다.




모노레일, 마을을 잇다 - 태백선 고한역[2019.08.30]


 강원도의 험한 산령을 굽이굽이 해체 나가면서 강릉으로 향하던 태백선 무궁화호는 남한에서 가장 높은 곳에 위치한 태백시의 추전역을 앞에 두고 정신군 고한읍에 위치한 고한역에 오게됩니다. 추전역이 운영되지 않고 있는 현재 우리나라에서 가장 높은 해발고도(705m)에 위치한 고한역은 높은 산이 많은 강원도의 특성을 잘 나타낸다고 할 수 있겠지요.



고한역은 마을에서 약간 높은 곳에 위치해 있습니다.



약간의 오르막길을 올라 고한역에 와보았습니다.



고한역 옆에는 객차를 가조한 시설이 위치해 있습니다.



고한역 앞에는 작은 맞이방이 마련되어 있습니다.



입구 한켠에 추리만화책들을 모아둔 책꽃이가 있습니다.



열차가 오지 않는 시간대의 고한역은 고요합니다.



열차는 하루에 10회 운행되고 있습니다.

청량리행은 오후 6시 39분이 막차입니다.



사북역에는 없던 고객대기실이 마련되어 있군요.



고한역의 오른쪽에 카페가 있군요.



고한역 앞에 있는 나무계단을 내려오면 마을사람들의 사진들이 붙어있습니다.



한산한 마을 안에 제법 큰 초등학교 건물이 보입니다.



과거 탄광촌이었던 고한읍에는 광부의 자녀들이 학교를 다녔겠지요.



고한시장의 입구



탄광촌이었던 마을의 특성을 시장에 잘 살려놓았습니다.



고한시장의 반대편에서 바라본 모습

마을을 돌아다니던 도중 배가고파 점심을 해결했습니다.








갈비탕이 상당히 맛이 좋았습니다.



고한읍내를 둘러 개천이 흐르고 있습니다.

탄광이 한창 운영될 때 이 천에는 검은 물이 흘렀다고 하더군요.



마을 인근에는 고한모노레일이 운행되고 있습니다.



모노레일은 생각보다 상당히 작아보입니다.

흔히 타는 엘리베이터 크기 정도랄까요?



모노레일에 직접 탑승해 보기로 하였습니다.



모노레일 차량 안에서 레일을 바라본 모습



차량 내부는 엘리베이터와 흡사하게 생겼습니다.

문을 열고 닫는 버튼이 엘리베이터의 그것과 똑같습니다.



오르막길을 올라갈때 생각보다 빠르더군요.



아랫쪽을 보니 상당히 가파른 언덕입니다.



읍내 너머를 자세히 보니 고한역이 보입니다.



모노레일은 다시 제가 탑승하였던 곳으로 이동합니다.



모노레일 바로 옆에 강원랜드 사무실이 있었군요.



마을 너머로 보이는 태백선



이제 다시 다리를 건어 고한역으로 돌아갑니다.


날이 어두워지자 카페 간판의 별에 불이 들어옵니다.



방금전에 보았던 그 객차의 뒷모습

다른데로 이동할 예정이 없는지 체인으로 꽁꽁 묶어놓고 있습니다.



이제 우리는 고한역을 떠납니다.



열차가 오기를 기다리며...



잠시후 청량리행 무궁화호 열차가 고한역으로 들어옵니다.



이제 기차에 몸을 싣고 집으로 돌아갑니다.

이 장소를 Daum지도에서 확인해보세요.
강원 정선군 고한읍 고한리 97-11 | 한쇼
도움말 Daum 지도

탄광의 마을에서 리조트의 마을로 - 태백선 사북역[2019.08.30]


 청량리를 출발하여 제천을 거쳐 강릉으로 가는 무궁화호는 우리나라가 산업화가 한창이던 시절 석탄을 수송하기 위한 목적으로 건설한 태선을 지나갑니다. 태백선은 1974년 전철화가 완료되어 힘 좋은 전기기관차가 석탄을 나르던 곳이기도 하지요. 그러나 석탄이 사양 사업이 되면서 수많은 사람들이 일자리를 잃게 되었고 특히 정선군 내에 대표적인 탄광촌이었던 사북과 고한은 활기를 잃고 맙니다.

 몰락하는 탄광촌을 막기 위해 정부는 인근에 리조트를 건설하고 한국인이 유일하게 출입이 허가된 카지노인 강원랜드를 만들게 되면서 사람들은 다시 마을로 몰려들었습니다. 리조트의 건설로 마을의 수입원이 다시 생기게 되었지만 도박중독 등의 사회적인 문제를 떠안게 되었습니다.



제천역에서 강릉행 무궁화호를 타고 사북역에 도착합니다.



예전에 새마을호가 다녔던 적이 있었는데 아직까지 타는곳에 표시가 되있네요.



역에서 내리자마자 호텔촌이 보입니다.



어느새 열차는 출발하기 위해 출입문을 닫습니다.



열차는 그렇게 고한역을 향해 달려갑니다.



고한역을 유유히 빠져나가는 무궁화호



사북역 안에는 탄광열차의 모형이 전시되어 있었습니다.



불과 몇십년전만 해도 이 동네에는 이러한 탄광열차가 돌아다니고 있었습니다.



이제 사북역사 내로 이동합니다.



역내에는 분식집이 있습니다.

콤프도 사용할 수 있었나보군요.



흔한 매표소의 모습



열차를 타러 가는 방향에서 바라본 모습



사북역 인근에는 주 수입원인 하이원 리조트가 위치해 있습니다.



역 밖을 나오자마자 마주친 대출광고를 보고 이 동네의 분위기를 짐작할 수 있었습니다.



마을 안은 호텔들이 잔뜩 늘어서 있었습니다.



주변이 호텔촌인 것에 비해 한적한 사북역



안그래도 좁은 마을에 폐건물이 역앞에 있으니 더 처량해 보입니다.



사북역 삼거리



좀 더 걸어가다 보면 사북시장이 보입니다.



사북역에서 가장 많이 보이는 가게는 전당사입니다.

그 다음으로 많이 보이는 가게는 마사지샵...



도박에 빠진 사람들을 위해 기도하는 교회도 있나봅니다.



강원랜드가 위치한 하이원리조트로 들어가는 입구



강원랜드의 현실을 보여주는 씁쓸한 사진

번호판을 떼인 차량이 주인을 하염없이 기다리고 있습니다.



정선군 또한 평창올림픽 개최지로 알파인 스키 경기가 개최되었습니다.

이 곳 사북에서는 거리가 어느정도 됩니다만

이 곳에서도 수호랑과 반다비가 있군요.



잠시 마을을 한 바퀴 돌다온 사이에 대출 명함들이 덕지덕지 붙어있습니다.



세상에나, 서울 면허의 택시가 이 외딴동네까지 와있습니다.

소문에 의하면 서울에서 강원랜드까지 10만원 이상을 주고 여기까지 온다는 분들이 계시더군요.


마을을 둘러본 후 또다른 탄광촌이었던 고한역으로 이동합니다.





환골탈태(換骨奪胎)를 위해여 - 제천임시역(4)[2019.08.30]


  중앙선과 충북선, 태백선을 달려온 모든 열차가 모이는 제천역은 전국의 여행객들이 한 번쯤은 거쳐가는 곳이라 할 수 있겠지요. 자신이 가고자 하는 방향에 따라서 이 곳에서 열차를 갈아타야 하기 때문이지요. 청량리에서 안동이나 태백으로 가시는 분이라면 열차에서 내릴 필요 없이 바로 갈 수 있지만 충북선을 이용해야 하는 경우 제천역이 충북선의 시종착역 역할을 하고 있기에 이 곳에서 자연스레 갈아타게 됩니다.


 충북선을 타고 제천역에서 정선으로 향하는 열차를 갈아타기 위해 이번에도 제천역을 방문하게 되었습니다. 지금까지 제천역을 방문하였던 이야기들은 아래의 글들을 보아주시길 바랍니다.



환골탈태(換骨奪胎)를 위해여 - 제천임시역(1)[2010.02.04]


환골탈태(換骨奪胎)를 위해여 - 제천임시역(2)[2018.10.28]


환골탈태(換骨奪胎)를 위해여 - 제천임시역(3)[2018.12.16]




지난번에 충북선 제천행 열차를 타고 왔을 땐 임시승강장에서 내렸었는데

이번에는 새로 개조한 승강장에 승객들을 내려줍니다.



마침 강릉행 무궁화호가 잠시후 도착하는데

환승하는 승객들의 편의를 위해 이곳에 내려준 것일까요?



물론 제천역이 목적지인 분들은 나가는곳으로 향합니다.



가만보니 제가 내린 승강장은 작년 12월까지만 해도 공사를 하느라 막아둔 곳이었습니다.



한동안 이 승강장을 쓸 예정인지 안내판에 '임시'라는 글씨까지 써두었습니다.



보아하니 기존의 승강장은 고상홈으로 탈바꿈 하였습니다.



임시승강장도 아직은 운영되고 있습니다.



승강장 공사는 완료되었지만 아직 계단 공사는 끝내지 못한듯 합니다.



작년까지 홀로 쓰이던 승강장도 벌써 고상홈 공사가 한창입니다.



고상홈 개량 공사가 끝나면 철거될 줄 알았던 지하통로가 아직도 사용되고 있습니다.



바로 옆 승강장 또한 고상홈으로 개량하고는 있으나 지하통로는 막아두지 않고 있습니다.



고상홈 승강장에 올라갈 수 있도록 나무계단으로 연결되어 있습니다.

신역사 계단 공사가 끝날 때 까지는 계속 사용될 듯 보입니다.



공사중인 승강장으로 가는 길은 현재 출입을 금지하고 있습니다.



휠체어 리프트가 있는데 과연 사용은 되고 있긴 할까요



역내 안내는 현재 공사 상황까지 반영하여 놓았습니다.

대전방면의 임시승강장까지 가는 길 또한 안내되고 있습니다.



잠시 쉬는 곳



제천역의 새로운 역사를 건설한지 벌써 1년반이 지났습니다.



이제 창문도 붙이고 생각보다 공사 속도는 빨라보입니다.

마음만 먹으면 2019년이 끝나기 전에는 개통될 것 같습니다만

언제나 그랬듯이 2020년에 개통되겠지요.



며칠전까지 휑하던 임시역사 뒷편도 건물이 올라가고 있습니다.



다시 승강장으로 돌아가 강릉행 열차를 타러 갑니다.



승강장까지 이동하는 통로도 골조는 모두 완성되었습니다. 이제 붙이기만 하면 될 듯!



어느새 강릉행 열차가 승강장으로 들어오고 있습니다.

저상흠 승강장은 지하통로에서 고상흠 끝까지 걸어가야 하기 때문에 역 내에도 열차 출발 15분 전까지는 도착하기를 권하고 있습니다.

실제로 나이 많으신 분들은 고상홈 승강장을 모두 건너오시는데 많이 힘들어 하십니다.



작년까지만 해도 특실로 운행되었던 1호차입니다.

이젠 무궁화호 특실이 사라졌지만 운이 좋다면 기존의 특실을 이용할 수 있습니다.


이제 다음 여행지인 사북역으로 향합니다.





Windows 10 Internet Explorer 11 에서 Adobe flash가 샐행되지 않을때 해결방법

공대생의 팁 2019. 8. 26. 13:31


 2020년 말부터 Chrome 웹브라우저가 Adobe에서 제공하는 Flash Player 지원을 종료한다고 하였습니다. 인터넷 초창기에 벡터 방식을 사용하여 저용량으로 고용량의 동영상보다도 고화질로 화면에 표현할 수 있다는 점 때문에 Flash는 상당히 많이 사용되었습니다.

 그러나 인터넷 속도가 점점 향상되면서 2019년 오늘날에는 유튜브와 같은 고화질 동영상을 무리없이 다운로드 받아 실시간으로 재생할 수 있는 환경이 만들어지면서 고도의 연산을 필요로 하는 Flash는 오늘날의 인터넷 환경에서는 빠른 속도를 요구하는 웹브라우저에도 어울리지 않게 되면서 점차 사용이 줄어들고 있는 상황입니다. 최근에 제작되는 사이트들도 다양한 디바이스 환경에서 접속되는 것을 대비하기 위해 반응형 사이트를 제작하기에 Flash를 사용하는 것은 더더욱 어려워졌습니다.


 그럼에도 아직까지 우리나라에서는 Flash가 사용되는 사이트들이 아직까지 존재하고 있습니다. Flash가 점점 퇴출되어가는 과정에서도 우리나라의 환경에서는 웹브라우저의 Flash 지원 종료는 조금은 불편하게 느껴지는 것은 사실입니다. 티스토리 또한 Flash를 사용하지 않는 새로운 웹에디터를 제공함으로서 (비록 새로운 에디터의 장단점을 떠나서) Flash의 의존에서 점차 멀어져 가고 있는 상황에서 말이지요. 이러한 우리나라의 인터넷 환경에서 Flash가 실행되지 않을 때 이를 해결하는 방법에 대해 다루어보겠습니다.


 먼저 아래의 링크를 클릭하셔서 Flash Player를 활성화합니다.



IE(Internet Explorer) 어도비플래시플레이어 설치 및 활성화 방법 안내

↑↑위 링크를 클릭해주세요



 만약 위의 링크대로 하였지만 여전히 실행이 되지 않을 경우 아래의 방법을 참고해주시길 바랍니다.





 2019년 8월 현재 Flash Player로 서비스를 제공하는 사이트들 중 하나인 더존 세금계산서(https://www.bill36524.com)를 Windows 10 운영체제에서 Internet Explorer 11로 접속한 후 로그인을 하게 되면







 위의 화면과 같이 '어도비 플래시 플레이어 꼭 설치 하셔야 합니다!'라는 경고문구와 함께 플래시를 설치할 것을 요구하고 있습니다.








 그러나 Windows 10에서 부터는 Internet Explorer 11에는 이미 Flash Player가 포함이 되어있기 때문에 별도의 설치를 하지 않더라도 Flash Player를 실행할 수 잇어야 합니다. 이는 Flash Player를 사용하려는 사이트에서 호환성에 문제가 발생히여 실행이 되지 않는 것으로 추측됩니다.






 혹시 인터넷 익스플로러의 버전 문제 때문일 가능성이 있어 Internet Explorer를 다른 버전으로 설치하려고 시도하였으나 Windows 10에서는 Internet Explorer가 기본프로그램으로 설정되어 있기 때문에 삭제할 수 없으며 게다가 이전 버전으로 다운그레이드를 시키는 것도 사실상 불가능합니다.


 그렇다면 Windows 10에서 Internet Explorer의 이전 버전을 실행할 수 있는 방법은 없을까요? Internet Explorer 11에서는 이전 버전의 환경에서 사이트에 접속할 수 있도록 하는 에물레이션 모드가 존재하여 11 이하의 버전의 환경에서 웹브라우저를 사용할 수 있습니다.


 Internet Explorer 11을 실행하신 다음 'F12'버튼을 누르시면 아래의 화면과 같이 개발자도구창이 나타납니다.





개발자도구창이 나타나면 메뉴에서 '에뮬레이션'을 클릭합니다.





에뮬레이션 창을 클릭하면 다음과 같은 창을 보실 수 있습니다. 위의 메뉴 중 '문서 모드'를 보시면 11(기본값)이라 적혀있는 부분을 보실 수 있습니다. 이는 해당 사이트를 Internet Explorer 11 모드에서 접속중이라는 의미입니다.





문서 모드에서 버전이 적혀있는 부분을 클릭하시면 11 이외의 모드를 선택할 수 있습니다. Internet Explorer 10 버전 모드에서 해당 사이트에 접속하고 싶다면 '10'을 선택해줍니다.






에뮬레이션 모드로 문서 모드를 11 이하의 버전으로 설정하여 접속하게 되면 이전과 같이 Flash Player를 설치해달라는 창이 나오지 않고 곧바로 플래시가 실행된 창이 나타나는 것을 확인하실 수 있습니다.