라즈베리파이 openCV 설치 및 관절 인식
https://www.raspberrypi.org/downloads/raspbian/
라즈베리파이 설치
- RASPBIAN STRETCH WITH DESKTOP AND RECOMMENDED SOFTWARE : LibreOffice, Scratch, SonicPi, Thonny, Mathematica 등 포함
- RASPBIAN STRETCH WITH DESKTOP : Chromium browser, VLC media player, Python 등 포함
- RASPBIAN STRETCH LITE : DESKTOP GUI 없는 버전
flash 클릭!
라즈베리의 초기 id 와 암호는
id : pi
passwd : raspberry
혹시 와이파이가 안되면
Preference>Raspberry Pi Configuration>Localisiation
에서 GB나 US등도 선택해보기 바람.
sudo apt install fonts-nanum fonts-nanum-extra
sudo apt install nabi
sudo apt install im-config
혹시 한글이 잘 안되면 아래 링크 참조
vncserver -geometry 1280x1024
CCTV 만들기
우선 open cv 를 설치해야 하는데 아래 두 링크를 참고해서 설치하면 된다.
https://webnautes.tistory.com/916
https://www.alatortsev.com/2018/09/05/installing-opencv-3-4-3-on-raspberry-pi-3-b/
아래는 설치에 쓰이는 코드인데 치기 귀찮아서 복사해둔 코드다.
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D WITH_TBB=OFF \
-D WITH_IPP=OFF \
-D WITH_1394=OFF \
-D BUILD_WITH_DEBUG_INFO=OFF \
-D BUILD_DOCS=OFF \
-D INSTALL_C_EXAMPLES=ON \
-D INSTALL_PYTHON_EXAMPLES=ON \
-D BUILD_EXAMPLES=OFF \
-D BUILD_TESTS=OFF \
-D BUILD_PERF_TESTS=OFF \
-D ENABLE_NEON=ON \
-D ENABLE_VFPV3=ON \
-D WITH_QT=OFF \
-D WITH_GTK=ON \
-D WITH_OPENGL=ON \
-D OPENCV_ENABLE_NONFREE=ON \
-D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.1.2/modules \
-D WITH_V4L=ON \
-D WITH_FFMPEG=ON \
-D WITH_XINE=ON \
-D ENABLE_PRECOMPILED_HEADERS=OFF \
-D BUILD_NEW_PYTHON_SUPPORT=ON \
-D OPENCV_GENERATE_PKGCONFIG=ON ../
https://www.youtube.com/watch?v=DIGwweDJCBk
https://www.youtube.com/watch?v=WgsZc_wS2qQ
pip3 install imutils
pip3 install imagezmq
client.py
import socket
import time
from imutils.video import VideoStream
import imagezmq
sender = imagezmq.ImageSender(connect_to='tcp://<서버 내부 IP>:5555')
rpi_name = socket.gethostname() # send RPi hostname with each image
picam = VideoStream(usePiCamera=True).start()
time.sleep(2.0) # allow camera sensor to warm up
while True: # send images as stream until Ctrl-C
image = picam.read()
sender.send_image(rpi_name, image)
pip install imagezmq
import imagezmq
conda install -c conda-forge imutils
server.py
import cv2
import imagezmq
image_hub = imagezmq.ImageHub()
while True:
rpi_name, image = image_hub.recv_image()
cv2.imshow(rpi_name, image)
if cv2.waitKey(1) == ord('q'):
break
image_hub.send_reply(b'OK')
아래와 같이 cctv 가 잘 나온다고 하면 이제 사진을 저장해보자.
import cv2
import imagezmq
from time import gmtime, strftime
image_hub = imagezmq.ImageHub()
while True:
rpi_name, image = image_hub.recv_image()
image2= cv2.resize(image, dsize=(640, 480), interpolation=cv2.INTER_AREA)
cv2.imshow(rpi_name, image2)
imgfile='D:/image/'+strftime("%Y%m%d_%H_%M_%S", gmtime())+'.png'
cv2.imwrite(imgfile, image2)
if cv2.waitKey(1) == ord('q'):
break
image_hub.send_reply(b'OK')
저장한 자료를 바탕으로 관절을 인식하는 코드
import torch
import torchvision
from torchvision import models
import torchvision.transforms as T
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
print('pytorch', torch.__version__)
print('torchvision', torchvision.__version__)
IMG_SIZE = 480
THRESHOLD = 0.95
model = models.detection.keypointrcnn_resnet50_fpn(pretrained=True).eval()
import os
d_list='D:/image/'
for ls in os.listdir(d_list):
print(ls)
img = Image.open(d_list+ls)
img = img.resize((IMG_SIZE, int(img.height * IMG_SIZE / img.width)))
plt.figure(figsize=(16, 16))
#plt.imshow(img)
trf = T.Compose([
T.ToTensor()
])
input_img = trf(img)
out = model([input_img])[0]
codes = [Path.MOVETO,Path.LINETO,Path.LINETO]
fig, ax = plt.subplots(1, figsize=(16, 16))
ax.imshow(img)
for box, score, keypoints in zip(out['boxes'], out['scores'], out['keypoints']):
score = score.detach().numpy()
if score < THRESHOLD:
continue
box = box.detach().numpy()
keypoints = keypoints.detach().numpy()[:, :2]
rect = patches.Rectangle((box[0], box[1]), box[2]-box[0], box[3]-box[1], linewidth=2, edgecolor='b', facecolor='none')
ax.add_patch(rect)
# 17 keypoints
for k in keypoints:
circle = patches.Circle((k[0], k[1]), radius=2, facecolor='r')
ax.add_patch(circle)
# draw path
# left arm
path = Path(keypoints[5:10:2], codes)
line = patches.PathPatch(path, linewidth=2, facecolor='none', edgecolor='r')
ax.add_patch(line)
# right arm
path = Path(keypoints[6:11:2], codes)
line = patches.PathPatch(path, linewidth=2, facecolor='none', edgecolor='r')
ax.add_patch(line)
# left leg
path = Path(keypoints[11:16:2], codes)
line = patches.PathPatch(path, linewidth=2, facecolor='none', edgecolor='r')
ax.add_patch(line)
# right leg
path = Path(keypoints[12:17:2], codes)
line = patches.PathPatch(path, linewidth=2, facecolor='none', edgecolor='r')
ax.add_patch(line)
plt.savefig('D:/model_output/'+ls)
아래와 같이 나타나면 성공
open cv와 결합해서 자동 디텍션은 추후 포스팅하는 걸로 하겠다.
'ubuntu' 카테고리의 다른 글
라즈베리파이 selenium 사용법 (0) | 2020.03.23 |
---|---|
nas 마운트하기 (0) | 2020.03.23 |
jupyter-notebook에서 matplotlib 한글폰트 설정 (0) | 2020.02.13 |
파이썬 Selenium linux 환경 구축하기 (ubuntu) (0) | 2019.05.08 |
우분투 팀뷰어 끊김 현상 (0) | 2019.03.26 |
jupyter-notebook에서 matplotlib 한글폰트 설정
from IPython.core.display import display, HTML
display(HTML("<style> .container{width:90% !important;}</style>"))
# !sudo apt install fonts-nanum
# !sudo fc-cache -fv
# 일부러 오류내서 경로 확인
matplotlib.font_manager.findfont('a')
# !sudo cp /usr/share/fonts/truetype/nanum/Nanum* /root/anaconda3/envs/jupyter/lib/python3.6/site-packages/matplotlib/mpl-data/fonts/ttf
import matplotlib
import matplotlib.font_manager
[f.name for f in matplotlib.font_manager.fontManager.ttflist if 'Nanum' in f.name]
import platform
from matplotlib import font_manager, rc
import matplotlib.pyplot as plt
# 한글 사용시 마이너스 폰트가 깨지는 문제가 발생할 수 있으므로 설정변경
plt.rcParams['axes.unicode_minus'] = False
if platform.system() == 'Windows':
path = "c:/Windows/Fonts/malgun.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
rc('font', family=font_name)
elif platform.system() == 'Darwin':
rc('font', family='AppleGothic')
elif platform.system() == 'Linux':
rc('font', family='NanumBarunGothic')
else:
print('Unknown system... sorry~~~~~~')
import os
import numpy as np
np.random.seed(0)
x=range(5)
y=10+5*np.random.randn(5)
fig=plt.figure()
ax=fig.add_subplot(111)
ax.set_title('한국어를 지정한 타이틀')
ax.bar(x,y)
plt.show()
'ubuntu' 카테고리의 다른 글
nas 마운트하기 (0) | 2020.03.23 |
---|---|
라즈베리파이 openCV 설치 및 관절 인식 (2) | 2020.02.13 |
파이썬 Selenium linux 환경 구축하기 (ubuntu) (0) | 2019.05.08 |
우분투 팀뷰어 끊김 현상 (0) | 2019.03.26 |
주피터 서버 만들기(최종) (0) | 2019.03.25 |
git 개념 및 명령어 정리
깃 환경 설정
깃은 버전을 저장할 때마다 해당 버전을 만든 사용자 정보를 함께 저장하므로 사용자 정보 입력이 필요.
사용자의 이름과 메일 주소를 저장하는 방법은 다음과 같다.
경로 관련 코드
cd : 경로를 변경하는 코드
pwd : 작업경로를 출력하는 코드
ls : 현재 디렉토리에 존재하는 파일이나 디렉토리를 확인하는 코드
ls 명령어 옵션
-a : 숨김 파일 및 디렉토리 함께 표시
-l : 파일, 디렉토리의 상세정보 함께 표시
-r : 정렬 순서를 거꾸로 표시
-t : 시간의 내림차순으로 표시
vim 사용법
vim은 터미널 창에서 경로를 이동하지 않고 키보드 입력만으로 스크립트를 수정할 수 있게 도와주는 편집기로 저장 및 종료를 담당하는 'ex모드'와 텍스트 입력 및 수정을 담당하는 '입력모드'로 구성되어 있다. vim을 실행하면 ex모드로 실행되기 때문에 입력하기 위해서는 I 나 A를 입력하여 입력모드로 변환이 필요하다. 또한 입력모드는 ESC를 통해 ex모드로 돌아갈 수 있으며, ':wq'을 통해 저장 후 종료 가능하다.
vim <생성할 스크립트 명.확장자>
깃의 명령어와 개념
우리는 문서작업을 할 때, 문서를 작업 경로에서 수정하거나 생성하고, 수정한 파일 중 유지하고 싶은 파일(버전)을 스테이지에 임시 저장하고, 그 중 최종파일을 저장소에 저장(커밋)한다.
버전 : 문서를 수정하고 저장할 때마다 생기는 스크립트
스테이지 : 버전으로 만들 파일이 대기하는 장소
작업 트리(작업 디렉토리) : 파일의 수정이나 저장 등의 작업을 하는 디렉토리
저장소 : 스테이지에서 대기하던 파일들을 버전으로 저장하는 곳
커밋 : 파일 수정을 끝내고 스테이지에 넣은 스크립트를 버전으로 만들기 위해 사용하는 명령어
- 스테이지와 저장소는 눈에서 보이지 않고 .git 디렉토리 안에 숨은 파일 형태로 존재
git init : 현재 디렉토리에 깃을 사용할 수 있도록 디렉토리를 초기화 하는 명령어
git status : 깃의 상태를 확인하는 명령어
- on branch master : 현재 master 브랜치에 있으며, 이는 저장소에 있는 디렉토리와 비슷한 개념
- No commits yet : 아직 커밋한 파일이 없음.
- nothing to commit : 현재 커밋할 파일이 없음.
- untracked files : 아직 한번도 버전 관리하지 않은 파일을 의미
git add : 파일을 만들거나 수정한 것을 스테이지에 파일을 추가하는 명령어
git commit : 스테이지에 있는 파일을 버전으로 만드는 깃 만드는 명령어
- 한번 커밋한 파일의 경우 파일을 재수정 후 다시 스테이지에 올리지 않고 -am 옵션을 활용해 스테이징과 커밋을 동시에 가능하며, --amend를 통해 커밋 메세지를 수정할 수 있음.
git log : 커밋 기록을 자세히 보기위한 명령어
git diff : 소스 코드가 너무 많아 어떤 부분이 버전별로 어떤 부분이 다른지 쉽게 찾아 볼 수 있도록 git diff를 활용할 수 있음.
tracked 파일과 untracked 파일
아래는 hello.txt를 수정하고 hello2.txt 를 생성한 상태에서 git status를 확인한 결과이다.
깃은 한번이라도 커밋한 파일을 지속적으로 수정 여부를 추적하므로 이를 tracked파일이라하며, 커밋된 적이 없던 자료를 untracked파일로 아래처럼 구분하여 표기한다.
'git add . ' 명령어를 통해 파일을 한꺼번에 스테이지에 올린 뒤 상태를 확인해보자.
tracked 파일과 untracked 파일 모두 스테이지에 올라온 것을 확인할 수 있다.
.gitignore 라는 파일을 생성하여 버전 관리에서 제외할 파일이나 디렉토리 목록을 넣어 둘 수 있음.
unmodified, modified, staged 상태
tracked 상태의 파일이 현재 작업트리에 있는지 스테이지에 있는지 등을 확인하는 방법을 알아보자.
아래와 같이 working tree clean이라 나타나면 이는 현재 트리에 있는 모든 파일의 상태가 수정되지 않은 상태임을 뜻함.
changes not stage for commit 이라는 메세지의 경우 트리에 있는 파일이 수정만 된 상태를 뜻함.
changes to be committed 라는 메세지의 경우 커밋 직전 단계인 staged 상태를 의미함.
작업 되돌리기
git checkout --<파일명.확장자> : 스테이지에 올라가지 않은 파일을 원래대로 돌리는 명령어
git reset HEAD <파일명.확장자> : 스테이징을 취소하는 명령어
git reset HEAD^ : 가장 최근에 한 커밋을 취소하는 명령어
git reset --hard <커밋 해시> : 특정 커밋으로 되돌리는 명령어
git revert <커밋 해시> : 커밋을 삭제하지 않고 되돌리는 명령어
얼마전에 로또분석에 관해 김태영님께서 포스팅한 딥러닝 글을 보게되었습니다. 평소에 로또나 주식에 관심이 많았기에 기존 분석방법을 고도화 하고 싶어 김태영님의 코드를 참고하였고, 이를 R버전으로 포스팅해보겠다. 물론 현재 이 외에 다양한 시도를 해보는 중이다.
참고 : 김태영님의 블로그
로또 자료 생성
로또 자료 생성의 경우 멀티 코어를 활용하였다. 또한 패키지 로드가 귀찮아 만들었던 개인 패키지를 활용하였다.
library(devtools)
if(!require(lotto))install_github('qkdrk777777/lotto')
if(!require(DUcj))install_github('qkdrk777777/DUcj')
# 개인 패키지
library(DUcj)
# DUcj::package 는 if(!require(패키지))install.packages('패키지')를 동시에 진행해주는 함수
package('progress')
package('XML')
package('stringr')
package('rvest')
package(parallel)
package(foreach)
package(rvest)
package(xml2)
url<-'https://search.naver.com/search.naver?sm=tab_hty.top&where=nexearch&query=%EB%A1%9C%EB%98%90&oquery=%EB%A1%9C%EB%98%90&tqi=TmrKidpVuFdsssc0EvVssssssUd-075322'
line<-read_html(url,encoding="UTF-8")
p1<-html_nodes(line,css='._lotto-btn-current em')%>%html_text()
last=as.numeric(substr(p1,1,3))
last
spec=detectCores()
spec
cl=makeCluster(spec)
data=NULL
try({url<-paste0('https://search.naver.com/search.naver?sm=tab_drt&where=nexearch&query=',last:1,'%ED%9A%8C%EB%A1%9C%EB%98%90')
c=data.frame(t(parApply(cl,MARGIN=1,data.frame(url),function(x){
(as.numeric(strsplit(stringr::str_trim(
rvest::html_text(rvest::html_nodes(xml2::read_html(x,encoding='UTF-8'),css='.num_box'))),split=' ')[[1]][1:6]))})))
data=cbind(num=last:1,c)},silent = T)
stopCluster(cl)
Sys.sleep(3)
# 멀티코어로 크롤링을 실패한 경우 다음과 같이 진행
if(is.null(data)){
url<-paste0('https://search.naver.com/search.naver?sm=tab_drt&where=nexearch&query=',last:1,'%ED%9A%8C%EB%A1%9C%EB%98%90')
data=NULL;t=0
for( i in url){t=t+1
data=rbind(data,data.frame(num=(last:1)[t],t(as.numeric(strsplit(stringr::str_trim(
rvest::html_text(rvest::html_nodes(xml2::read_html(i,encoding = 'UTF-8'),css='.num_box'))),split=' ')[[1]][1:6]))))
message(t/last*100)
}
}
분석용 데이터 생성
library(plyr)
rm(list=setdiff(ls(),c('data','data2')))
data2<-matrix(0,ncol=45,nrow=nrow(data));colnames(data2)=1:45
for(i in 1:nrow(data2)){
for(j in 2:7){
for(k in 1:45){
if(data[i,j]==k)data2[i,k]<-1}}}
rownames(data2)<-nrow(data2):1
library(keras)
library(tensorflow)
library(reticulate)
delete=data2
train=data.matrix(delete[-1,])
train=array(train,dim=c(1,nrow(train),ncol(train)))
target=data.matrix(delete[-nrow(data),])
모델 생성
model=keras_model_sequential()%>%
layer_cudnn_lstm(units=128,batch_input_shape=c(1,1,dim(train)[3]),return_sequences = F,stateful = T)%>%
layer_dense(units=45,activation = 'sigmoid')
model %>% compile(
optimizer = "adam",
loss = 'binary_crossentropy')
M_train_loss=c()
M_val_loss=c()
for(epoch in 1:150){
model$reset_states()
train_loss=c()
for (i in 1:700){
xs=train[,i,,drop=F]
ys=target[i,,drop=F]
train_loss=c(loss,model$train_on_batch(xs,ys))}
val_loss=c()
for( i in 700:800){
xs=train[,i,,drop=F]
ys=target[i,,drop=F]
val_loss=c(loss,model$test_on_batch(xs,ys))}
M_train_loss=c(M_train_loss,mean(train_loss))
M_val_loss=c(M_val_loss,mean(val_loss))
print(epoch)
}
matplot(cbind(M_train_loss,M_val_loss),type='l')
ls=list()
for(i in 1:800){
source('./MC_test.R')
}
ls1=ls
ls=list()
for(i in (800:(nrow(data)-1))){
source('./MC_test.R')
}
ls2=ls
table(rep(names(unlist(ls1)),unlist(ls1)))
table(rep(names(unlist(ls2)),unlist(ls2)))
i=dim(train)[2]
ball_list=c()
for(j in 1:10){
xs=train[,i,,drop=F]
pred=model$predict_on_batch(xs)
ball_box=rep(1:45,as.integer(pred*100+1))
selected_balls=c()
while(T){
if (length(selected_balls)==6) break
ball=sample(ball_box,1)
if (!(ball %in% selected_balls)) selected_balls=c(selected_balls,ball)
}
ball_list=rbind(ball_list,sort(selected_balls))
}
source 코드로 사용된 MC_test.R 코드
검증 코드는 편의상 2등을 생략하였는데 크롤링으로 데이터 생성할 때부터 보너스번호를 고려하여 짜면 될거 같다.
rank=c()
for(j in 1:10){
xs=train[,i,,drop=F]
pred=model$predict_on_batch(xs)
ball_box=rep(1:45,as.integer(pred*100+1))
selected_balls=c()
while(T){
if (length(selected_balls)==6) break
ball=sample(ball_box,1)
if (!(ball %in% selected_balls)) selected_balls=c(selected_balls,ball)
}
selected_balls
temp=length(intersect(selected_balls,data[i+1,-1]))
rank=c(rank,ifelse(temp==6,'1등',ifelse(temp==5,'3등',ifelse(temp==4,'4등',ifelse(temp==3,'5등',NA)))))
}
# print(i)
ls[[i]]=table(rank)
참고 : loss function 을 커스터마이징하여 분석을 진행할때 활용한 코드
lossfunction <- function(y, t) {
k_binary_crossentropy(y,t)
}
model %>% compile(
optimizer = "adam",
loss = function(y_true, y_pred)
lossfunction(y_true, y_pred)
)
'R' 카테고리의 다른 글
automap::autoKrige fit.method 의존성 문제 (0) | 2020.07.18 |
---|---|
R 스케줄링 (0) | 2020.01.19 |
[ggplot2] 시각화 정리 (0) | 2019.12.08 |
[R][SQL] RMariaDB 외부 접속 설정하기 (0) | 2019.11.29 |
[공간정보오픈플렛폼]위경도 변환, 주소 변환 (0) | 2019.11.27 |
tensorflow loss function 조절
#data generate
library(tensorflow)
library(keras)
#holdout cross validation
set.seed(1)
data=data.frame(norm=sort(rnorm(100)),uni=sort(runif(100)))
set.seed(1)
idx=sort(sample(1:nrow(data),0.7*nrow(data)))
trainData=data[idx,]
testData=data[-idx,]
target='uni'
#standardization
source('/home/ducj/standard.R')
#normalization
train=trainData
min=apply(train[,sapply(train,is.numeric)],2,min)
max=apply(train[,sapply(train,is.numeric)],2,max)
xy=names(which(sapply(train,is.numeric)))
train_data=t(apply(train[,xy],1,function(x){(x-min)/(max-min)}))
train_data=as.matrix(train_data[,colnames(train_data)!=target])
train_label=as.matrix(train[,target,drop=F])
test=testData
test_data=t(apply(test[,xy],1,function(x){(x-min)/(max-min)}))
test_data=as.matrix(test_data[,colnames(test_data)!=target])
test_label=as.matrix(test[,target,drop=F])
train_data=array(train_data,dim=c(nrow(train_data),ncol(train_data),1))
train_label=array(train_label,dim=c(nrow(train_data),1))
test_data=array(test_data,dim=c(nrow(test_data),ncol(test_data),1))
test_label=array(test_label,dim=c(nrow(test_data),1))
#custom loss function
# quantile <- 0.5
# loss <- function(q, y, f) {
# e <- y - f
# k_mean(k_maximum(q * e, (q - 1) * e), axis = 2)
# }
loss <- function(y, f) {
e <- y - f
k_mean(e^2, axis = 2)
}
sess <- k_get_session()
ys <- k_constant(c(1,2,3,4), shape = c(2,2))
yhats <- k_constant(c(1,3,3,4), shape = c(2,2))
sess$run(loss( ys, yhats))
#dnn
dim(train_data)=c(nrow(train_data),ncol(train_data))
dim(test_data)=c(nrow(test_data),ncol(test_data))
model =keras_model_sequential()%>%
layer_dense(units = 3, input_shape = c(ncol(train_data)),activation = 'sigmoid') %>%
# layer_activation_leaky_relu() %>%
layer_dense(units = 1)
model %>% compile(
optimizer = "adam",
loss = function(y_true, y_pred)
loss(y_true, y_pred),
metrics = "mae"
)
# history <-model %>% fit(train_data,train_label,epochs = 120,batch_size = 10)
history <-model %>% fit(train_data,train_label,epochs = 500,batch_size = 5,validation_data=list(test_data,test_label))
history <-model %>% fit(train_data,train_label,epochs = 1,batch_size = 70,validation_data=list(test_data,test_label))
pred=predict(model,test_data)
spTimer::spT.validation(z=test$uni,zhat=(pred-min[2])/(max[2]-min[2]))
#lstm
dim(train_data)=c(nrow(train_data),ncol(train_data),1)
dim(test_data)=c(nrow(test_data),ncol(test_data),1)
model=keras_model_sequential()%>%
layer_cudnn_lstm(units=3,input_shape=c(ncol(train_data),1))%>%
layer_dense(units=1)
model %>% compile(
optimizer = "adam",
loss = function(y_true, y_pred)
loss(y_true, y_pred),
metrics = "mae"
)
# model%>%compile(optimizer=optimizer_rmsprop(),loss='mae')
# model%>%compile(optimizer=optimizer_adam(lr=0.003),loss='mae')
history=model %>% fit(train_data,train_label, epochs=500, batch_size=5,validation_data=list(test_data,test_label))
pred2=predict(model,test_data)
spTimer::spT.validation(z=test$uni,zhat=(pred2-min[2])/(max[2]-min[2]))
plot(test$uni,type='l',ylab='unif')
lines((pred-min[2])/(max[2]-min[2]),type='l',col='2')
lines((pred2-min[2])/(max[2]-min[2]),type='l',col='3')
보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.