배경
문제 설명
목표
범위
이미지 로드 및 준비
이미지 포맷 및 변환
특징 추출 및 선택
로지스틱 회귀
선형 판별 분석
K 최근접이웃
의사결정 트리
랜덤 포레스트
나이브 베이즈
서포트 벡터 머신
정확성 및 성능 지표
머신러닝 모델 비교
한계와 과제
성과 요약
기여와 의의
향후 작업 및 개선 사항
식물 질병은 농업 생산성에 심각한 위협을 가해 농민들에게 수확량 손실과 경제적 어려움을 초래합니다. 효과적인 질병 관리 전략을 구현하고 작물 피해를 최소화하려면 식물 질병을 적시에 정확하게 감지하는 것이 중요합니다. 질병을 진단하는 기존의 수동 방법은 시간이 많이 걸리고 주관적이며 오류가 발생하기 쉽습니다. 따라서 기계 학습 및 이미지 처리와 같은 기술의 통합은 식물 질병 감지를 자동화하고 향상시키는 유망한 접근 방식으로 부상했습니다.
본 프로젝트의 주요 목표는 머신러닝 알고리즘과 이미지 처리 기술을 활용한 식물 질병 탐지 시스템을 개발하는 것입니다. 이 시스템은 잎의 디지털 이미지를 분석하여 식물 잎의 건강한 잎과 병든 잎을 정확하게 분류하는 것을 목표로 합니다. 감지 프로세스를 자동화함으로써 농부와 농업 전문가는 적시에 개입하고 작물 관리 관행을 최적화할 수 있도록 식물 질병을 신속하게 식별하고 해결할 수 있습니다.
이 프로젝트의 주요 목적은 다음과 같습니다
강력하고 정확한 식물 질병 탐지 시스템 개발
식물 잎의 자동 분류를 위한 기계 학습 알고리즘 구현
이미지 처리 기술을 활용하여 나뭇잎 이미지에서 관련 특징 추출
다양한 기계 학습 모델의 성능과 정확성을 평가합니다.
시스템과 쉽고 직관적인 상호작용을 위한 사용자 친화적인 인터페이스 제공
이 프로젝트는 특히 사과 잎에서 식물 질병을 검출하는 데 중점을 둡니다. 모델 훈련 및 테스트에 사용되는 데이터 세트는 건강한 사과 잎과 Apple Scab, Black Rot 및 Cedar Apple Rust와 같은 질병에 영향을 받는 잎의 이미지가 포함된 Plant-Village 데이터 세트에서 얻습니다. 시스템은 높은 정확도를 달성하는 것을 목표로 합니다. 질병을 분류하고 농부와 농업 전문가가 식물 질병을 효과적으로 식별하고 관리할 수 있는 실용적인 도구를 제공합니다. 이 프로젝트는 현장에서의 실시간 질병 감지 또는 이미지 획득을 위한 하드웨어 장치 통합을 다루지 않습니다.
이 식물 질병 탐지 시스템에 사용된 데이터 세트는 Plant-Village 데이터 세트에서 얻은 사과 잎 이미지로 구성됩니다. 데이터 세트는 사과 잎 조건 Apple___Apple_scab, Apple___Black_rot, Apple___Cedar_apple_rust 및 Apple___healthy의 다양한 클래스를 나타내는 네 가지 주요 범주로 구성됩니다.
Apple___Apple_scab: 이 카테고리에는 630개의 이미지가 포함되어 있으며, 598개의 이미지는 훈련용으로 할당되고 32개의 이미지는 테스트용으로 할당되었습니다.
Apple___Black_rot: 데이터 세트에는 이 카테고리의 621개 이미지가 포함되어 있으며, 589개 이미지는 훈련용으로 할당되고 32개 이미지는 테스트용으로 할당됩니다.
Apple___Cedar_apple_rust: 데이터 세트는 삼나무 사과 녹병의 영향을 받은 잎 이미지 275개로 구성되어 있으며, 261개 이미지는 훈련에 사용되고 14개 이미지는 테스트에 사용됩니다.
Apple___healthy: 이 카테고리에는 건강한 사과 잎 이미지 1645개가 포함되어 있습니다. 이 중 1562개의 이미지는 학습용으로 지정되고 83개의 이미지는 테스트용으로 예약됩니다.
훈련 이미지는 기계 학습 모델이 패턴을 인식하고 건강한 잎과 병든 잎을 구별하도록 가르치는 데 활용됩니다. 테스트 이미지는 보이지 않는 데이터에 대해 훈련된 모델의 성능과 정확성을 평가하는 데 사용됩니다. 이 다양한 데이터 세트를 활용하여 식물 질병 탐지 시스템은 사과 잎을 건강한 것으로 또는 사과 딱지, 검은 부패 또는 사과 잎과 같은 질병에 영향을 받는 것으로 정확하게 분류하는 것을 목표로 합니다. 삼나무 사과 녹. 데이터 세트의 구성을 통해 시스템은 다양한 잎 상태로부터 학습하고 식물 질병을 정확하게 일반화하고 식별하는 능력을 향상시킬 수 있습니다.
이미지 로드 및 준비
사과잎 질병 탐지 프로젝트의 첫 번째 단계는 다양한 질병에 영향을 받는 사과 잎의 이미지로 구성된 데이터세트를 획득하는 것입니다. 그런 다음 이러한 이미지는 추가 처리를 위해 액세스할 수 있도록 시스템에 로드됩니다. 또한 일관된 해상도로 크기 조정, 불필요한 부분 자르기, 색상 분포 정규화 등 필요한 조정을 수행하여 이미지를 준비합니다. 이미지 형식 및 변환 사과 잎 이미지가 로드되면 형식을 지정하고 변환해야 합니다. 프로젝트의 후속 단계와의 호환성. 여기에는 이미지 형식을 JPEG 또는 PNG와 같은 특정 파일 형식으로 변환하여 표준화하는 작업이 포함됩니다. 또한 일관성을 보장하고 정확한 분석을 촉진하기 위해 색 공간 해상도 또는 기타 이미지 속성을 조정할 수 있습니다.
특징 추출 및 선택
특징 추출은 사과 잎에서 질병을 탐지하는 데 중요한 단계입니다. 나뭇잎 이미지에서 관련 특징을 추출하기 위해 다양한 기술이 사용됩니다. 이러한 기술에는 질병과 관련된 질감 패턴을 포착하기 위한 질감 분석, 특정 질병과 관련된 변이를 식별하기 위한 색상 검사, 잎 형태의 불규칙성을 감지하기 위한 모양 연구 등이 포함됩니다. 이러한 독특한 특징을 추출함으로써 후속 기계 학습 알고리즘은 건강한 사과 잎과 병든 사과 잎을 효과적으로 구별할 수 있습니다.
기능 선택
이 단계에는 관련성과 판별력을 기반으로 추출된 특징의 하위 집합을 선택하는 작업이 포함됩니다. 기능 선택은 노이즈나 중복 정보를 제거하여 데이터세트의 차원성을 줄이는 데 도움이 됩니다. 가장 유익한 특징을 선택함으로써 질병 탐지 모델의 효율성과 정확성을 향상시킬 수 있습니다.
사과잎 질병 탐지 프로젝트는 다양한 기계 학습 알고리즘을 활용하여 효과적인 질병 분류 모델을 개발합니다. 다음 알고리즘이 사용됩니다.
로지스틱 회귀(Logistic Regression): 로지스틱 회귀는 추출된 특징을 기반으로 사과 잎이 건강하거나 질병에 걸릴 확률을 예측하는 데 사용됩니다.
선형 판별 분석: 선형 판별 분석은 건강한 샘플과 질병에 걸린 샘플을 가장 잘 분리하는 특징의 선형 조합을 찾아 사과 잎을 분류하는 데 도움이 됩니다.
KNN(K Nearest Neighbors): K Nearest Neighbors는 사과 잎의 특징을 특징 공간의 가장 가까운 이웃의 특징과 비교하여 분류합니다.
의사결정 트리: 의사결정 트리는 일련의 if-else 조건을 사용하여 해당 기능과 계층 관계를 기반으로 샘플을 분류합니다.
Random Forest: Random Forest는 분류 정확도를 높이기 위해 여러 의사결정 트리를 결합하는 앙상블 학습 방법입니다.
Naive Bayes: Naïve Bayes는 사과 잎이 특정 질병 클래스에 속할 확률을 계산하는 확률 알고리즘입니다.
SVM(지원 벡터 머신): 지원 벡터 머신은 사과 잎을 분류하기 위해 고차원 특징 공간에 초평면을 구성합니다.
기계 학습 알고리즘을 선택한 후 모델은 해당 질병 라벨이 있는 사과 잎 이미지로 구성된 라벨이 지정된 데이터 세트를 사용하여 훈련됩니다. 모델은 이 훈련 단계에서 특징과 질병 클래스 간의 패턴과 관계를 인식하는 방법을 학습합니다. 모델의 신뢰성과 일반화를 보장하기 위해 검증 프로세스가 수행됩니다. 훈련된 모델은 훈련 중에 사용되지 않은 별도의 검증 데이터 세트를 사용하여 평가됩니다. 이는 보이지 않는 사과 잎 샘플을 정확하게 분류하는 모델의 능력을 평가하는 데 도움이 됩니다.
모델이 훈련되고 검증되면 보이지 않는 새로운 사과 잎 이미지가 포함된 별도의 테스트 데이터 세트에서 테스트됩니다. 모델은 각 샘플의 질병 분류를 예측하고 정확도, 정밀도, 재현율, F1 점수 등의 성능 평가 지표를 계산하여 모델의 질병 탐지 유효성을 측정합니다.
[1]에서:
# ---------# 전역 기능 추출# --------- -------------from sklearn.preprocessing import LabelEncoderfrom sklearn.preprocessing import MinMaxScaleimport numpy as npimport mahotasimport cvimport osimport h5py# ----- ---------------# tunable-parameters# -------images_per_class = 800fixed_size = tuple(( 500 , 500 ))train_path = "../dataset/train"test_path = "../dataset/test"h5_train_features = "../embeddings/features/features.h5"h5_train_labels = "../embeddings/labels/labels.h5 "bins = 8##### BGR을 RGB로 변환In [2]:# 각 이미지를 BGR에서 RGB로 변환 formatdef rgb_bgr(image):rgb_img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)return rgb_img##### RGB에서 HSV(색상 채도 값)로 변환 ConversionIn [3]:# RGBdef bgr_hsv(rgb_img)에서 HSV 이미지 형식으로 변환 :hsv_img = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2HSV)return hsv_img##### 녹색 및 갈색 색상 추출을 위한 Image SegmentationIn [4]:#def img_segmentation(rgb_img, hsv_img):lower_green = np.array([ 25 , 0 , 20 ])upper_green = np.array([ 100 , 255 , 255 ])healthy_mask = cv2.inRange(hsv_img, lower_green, upper_green)result = cv2.bitwise_and(rgb_img, rgb_img, 마스크=healthy_mask)lower_brown = np.array([ 10 , 0 , 10 ])upper_brown = np.array([ 30 , 255 , 255 ])disease_mask = cv2.inRange(hsv_img, lower_brown, upper_brown)disease_result = cv2.bitwise_and(rgb_img, rgb_img, 마스크=disease_mask)final_mask = healthy_mask + disease_maskfinal_result = cv2.bitwise_and(rgb_img, rgb_img, 마스크=final_mask)return final_result##### 기능 설명자 결정###### 1. Hu MomentsIn [5]:# feature-descriptor-1: Hu Momentsdef fd_hu_moments( 이미지):이미지 = cv2.cvtColor(이미지, cv2.COLOR_BGR2GRAY)feature = cv2.HuMoments(cv2.moments(image)).platten()return feature###### 2. Haralick TexturesIn [6]:# feature-descriptor-2: Haralick Texturedef fd_haralick(image) :회색 = cv2.cvtColor(이미지, cv2.COLOR_BGR2GRAY)haralick = mahotas.features.haralick(gray).mean(axis= 0 )return haralick###### 3. 색상 HIstogramIn [7]:# feature-descriptor-3: 색상 히스토그램def fd_histogram(image, 마스크=None):image = cv2.cvtColor(이미지, cv2.COLOR_BGR2HSV)hist = cv2.calcHist( [이미지], [ 0 , 1 , 2 ], 없음, [bins, bins, bins], [ 0 , 256 , 0 , 256 , 0 ,256 ] )cv2.normalize(hist, hist)return hist.flaten()##### 훈련 데이터 세트 로드In [8]:# 훈련 레이블 가져오기train_labels = os.listdir(train_path)# 훈련 레이블 정렬train_labels.sort() print(train_labels)# 특징 벡터와 라벨을 보관할 빈 목록global_features = []labels = [] ['Apple___Apple_scab', 'Apple___Black_rot', 'Apple___Cedar_apple_rust','Apple___healthy']##### 데이터 세트에서 기능 및 레이블 임베딩 생성In [9]:# train_labels의 training_name에 대한 훈련 데이터 하위 폴더를 반복합니다.# Join 훈련 데이터 경로 및 각 종 훈련 폴더img_dir_path = os.path.join(train_path, training_name)# 현재 훈련 레이블을 가져옵니다.current_label = training_name# os.listdir(img_dir_path)의 img에 대해 각 하위 폴더의 이미지를 반복합니다.# 이미지 파일 이름을 가져옵니다.file = os.path.join( img_dir_path, img)# 이미지를 읽고 고정 크기로 크기를 조정합니다.image = cv2.imread(file)image = cv2.resize(image, 고정_크기)# 비트별로 함수 비트 실행RGB_BGR = rgb_bgr(이미지)BGR_HSV = bgr_hsv(RGB_BGR)IMG_SEGMENT = img_segmentation(RGB_BGR, BGR_HSV)# 전역 기능 설명자 호출fv_hu_moments = fd_hu_moments(IMG_SEGMENT)fv_haralick = fd_haralick(IMG_SEGMENT)fv_histogram = fd_histogram(IMG_SEGMENT)# 전역 기능 연결global_feature = np.hstack([fv_histogram, fv_haralick,fv_hu_moments])# 레이블 및 기능 벡터 목록 업데이트labels.append(current_label)global_features.append(global_feature)print(" [상태] 처리된 폴더: {}".format(current_label))print("[STATUS] 전역 기능 추출이 완료되었습니다...") [STATUS] 처리 폴더: Apple___Apple_scab[STATUS] 처리 폴더: Apple___Black_rot[STATUS] 처리 폴더: Apple___Cedar_apple_rust[STATUS] 처리 폴더: Apple___healthy[STATUS] 완료 전역 기능 추출...In [10]:# print(global_features)In [ 41]:# 전체 특징 벡터를 가져옵니다. sizeprint("[STATUS] 특징 벡터 크기{}".format(np.array(global_features).shape)) [STATUS] 특징 벡터 크기 (3010, 532)[12]에서:# 전체 훈련 레이블 크기를 가져옵니다# print(labels)print("[STATUS] training Labels {}".format(np.array(labels).shape )) [상태] 학습 라벨(3010,)
라벨 인코딩된 값Apple___Apple_scab 0Apple___Black_rot 1Apple___Cedar_apple_rust 2Apple___healthy 3
[13]에서:
targetNames = np.unique(labels) le = LabelEncoder() target = le.fit_transform(labels) print(targetNames) print("[STATUS] 훈련 레이블이 인코딩됨...") ['Apple___Apple_scab' 'Apple___Black_rot' 'Apple___Cedar_apple_rust' ' Apple___healthy'] [STATUS] 훈련 라벨이 인코딩되었습니다...
[14]에서:
sklearn.preprocessing에서 import MinMaxScalerscaler = MinMaxScaler(feature_range=( 0 , 1 ))rescaled_features = scaler.fit_transform(global_features)print("[STATUS] 기능 벡터 정규화...")rescaled_features[STATUS] 기능 벡터 정규화...Out [14]:배열([[0.8974175 , 0.03450962, 0.01845123, ..., 0.02027887, 0.12693291,0.96573218], [0.89815922, 0.13025558, 0.02774864, ..., 0.02027767, 0.12692423,0.96573354], [0.56777027, 0. , 0.01540143, ..., 0.02027886, 0.12693269,0.96573218], ..., [0.95697685, 0.01228793, 0.00548476, ..., 0.02027886, 0.12693346,0.96573218], [0.97704002, 0.10614054, 0.03136325, ..., 0.02027885, 0.12692424,0.96573217], [0.95214074, 0.03819411, 0.03671892, ..., 0.02027886, 0.12692996,0.96573217]])print("[STATUS] 대상 레이블: {}".format(target))print("[STATUS] 대상 레이블 모양: {}" .format(대상.모양)) [STATUS] 대상 라벨: [0 0 0 ... 3 3 3] [상태] 대상 라벨 모양: (3010,)
에이. 특징
h5f_data = h5py.File(h5_train_features, "w")h5f_data.create_dataset("dataset_1", data=np.array(rescaled_features))Out[16]:<HDF5 데이터 세트 "dataset_1": 모양 (3010, 532), 유형 " <f8">
h5f_label = h5py.File(h5_train_labels, "w")h5f_label.create_dataset("dataset_1", data=np.array(target))Out[17]:<HDF5 데이터 세트 "dataset_1": 모양 (3010,), 유형 "< i8">[43]에서:h5f_data.close()h5f_label.close()
# ---------# 모델 훈련# --------- -------------import h5py import numpy as npiimport osimport cvimport warningsfrom matplotlib import pyplotfrom sklearn.model_selection import train_test_split, cross_val_scorefrom sklearn.model_selection import KFold, StratifiedKFoldfrom sklearn.metrics 가져오기 혼란_매트릭스, Acclearn.linear_model에서 Accuracy_score,classification_report 가져오기 sklearn.tree에서 LogisticRegression 가져오기 sklearn.ensemble에서 DecisionTreeClassifier 가져오기 sklearn.neighbors에서 RandomForestClassifier 가져오기 sklearn.neighbors에서 KNeighborsClassifier 가져오기 sklearn.차별 분석에서 선형 판별 분석 가져오기 sklearn.naive_bayes에서 가져오기 GaussianNBfrom sklearn.svm 가져오기 SVCimport joblibwarnings.filterwarnings("ignore")# -------# tunable-parameters# --------------- -----num_trees = 100test_size = 0.seed = 9scoring = "accuracy"# 훈련 레이블 가져오기train_labels = os.listdir(train_path)# 훈련 레이블 정렬train_labels.sort()그렇지 않은 경우 os.path.exists(test_path):os.makedirs(test_path)# 모든 기계 학습 모델 생성models = []models.append(("LR", LogisticRegression(random_state=seed)))models.append(("LDA" , 선형 판별 분석()))models.append(("KNN", KNeighborsClassifier()))models.append(("DTC", DecisionTreeClassifier(random_state=seed)))models.append(("RF", RandomForestClassifier(n_estimators=num_trees,random_state=seed)))models.append(("NB ", GaussianNB()))models.append(("SVM", SVC(random_state=seed)))# 결과 및 이름을 보유하는 변수results = []names = []# 특징 벡터 및 훈련된 레이블 가져오기h5f_data = h5py.File(h5_train_features, "r")h5f_label = h5py.File(h5_train_labels, "r")global_features_string = h5f_data["dataset_1"]global_labels_string = h5f_label["dataset_1"]global_features = np.array(global_features_string)global_labels = np.array(global_labels_string)h5f_data.close()h5f_label.close()# 특징 벡터와 labels의 모양을 확인합니다.print("[STATUS] 특징 모양 : {}".format(global_features.shape))print("[상태] 레이블 모양: {}".format(global_labels.shape))print("[STATUS] 훈련이 시작되었습니다...")print(global_labels, len(global_labels), len(global_features)) [상태] 특징 모양: (3010, 532) [상태] 라벨 모양: (3010,) [상태] 훈련이 시작되었습니다... [0 0 0 ... 3 3 3] 3010 3010
[38]에서:
(trainDataGlobal,testDataGlobal,trainLabelsGlobal,testLabelsGlobal, ) = train_test_split(np.array(global_features), np.array(global_labels),test_size=test_size, random_state=seed)print("[STATUS] 분할된 학습 및 테스트 데이터...")print("학습 데이터: {} ".format(trainDataGlobal.shape))print("테스트 데이터: {}".format(testDataGlobal.shape)) [STATUS] 분할된 열차 및 테스트 데이터...학습 데이터: (2408, 532)테스트 데이터: (602, 532)In [40]:trainDataGlobalOut[40]:array([[9.47066972e-01, 1.97577832e-02) , 5.34481987e-04, ...,2.02788613e-02, 1.26936845e-01, 9.65732178e-01], [9.67673181e-01, 4.20456024e-02, 5.76285634e-02, ...,2.02788294e-02, 1.26933581e-01, 9.65732217e-01], [9.84705756e-01, 2.97800312e-02, 1.34500344e-02, ...,2.02788553e-02, 1.26941878e-01, 9.65732187e-01], ..., [8.64347882e-01, 5.89053245e-02, 4.27430333e-02, ...,2.02791643e-02, 1.26961451e-01, 9.65733689e-01], [9.85818416e-01, 1.47428536e-03, 3.35008392e-03, ...,2.02767694e-02, 1.26792776e-01, 9.65732951e-01], [9.93152188e-01, 1.31020292e-03, 8.50637768e-04, ...,2.02910354e-02, 1.27475382e-01, 9.65721108e-01]])
[22]에서:
이름, 모델의 모델:kfold = KFold(n_splits= 10)cv_results = cross_val_score(model, trainDataGlobal, trainLabelsGlobal, cv=kfold,scoring=scoring)results.append(cv_results)names.append(name)msg = "%s : %f (%f)" % (이름, cv_results.mean(), cv_results.std())print(msg)LR: 0.900346 (0.020452)LDA: 0.892038 (0.017931)KNN: 0.884978 (0.019588)CART: 0.886210 (0.014771)RF: 0.967191 (0.012676)NB: 0.839293 (0.014065)SVM: 0.885813 (0.021190)
[23]에서:
fig = pyplot.Figure()fig.suptitle("기계 학습 알고리즘 비교")ax = fig.add_subplot( 111 )pyplot.boxplot(results)ax.set_xticklabels(names)pyplot.show()
위 결과에서 Random Forest Classifier 모델이 96.7%로 가장 높은 정확도를 보이고 Gaussian NB 모델이 83.9%로 가장 낮은 정확도를 갖는 것을 알 수 있습니다.
<div class="highlighthighlight-source-python notranslate position-relative Overflow-auto" dir="auto" data-snippet-clipboard-copy-content=" [24]에서: clf = RandomForestClassifier(n_estimators=num_trees, random_state= 시드) [25]: clf.fit(trainDataGlobal, trainLabelsGlobal) len(trainDataGlobal), len(trainLabelsGlobal) Out[25]: (2408, 2408) In [34]: y_predict = clf.predict(testDataGlobal) testLabelsGlobal Out[34]: array([3, 3, 1, 3, 0, 3, 1, 1, 2, 1, 1, 0, 1, 3, 3, 3, 3, 2, 0, 2, 0, 3, 3, 3, 3, 3, 3, 3, 1, 3, 1, 1, 3, 3, 1, 3, 3, 3, 2, 3, 1, 3, 3, 3, 1, 0, 0, 3, 1, 3, 3, 0, 3, 3, 2, 3, 0, 3, 1, 0, 3, 0, 3, 3, 1, 3, 3, 0, 3, 3, 0, 3, 3, 3, 3, 2, 1, 1, 2, 0, 2, 1, 1, 0, 0, 3, 2, 0, 3, 2, 3, 3, 2, 3, 3, 1, 1, 3, 2, 0, 2, 1, 1, 2, 3, 3, 3, 1, 1, 0, 3, 0, 3, 3, 0, 3, 3, 3, 1, 2, 3, 2, 3, 0, 3, 0, 3, 1, 3, 3, 3, 3, 3, 2, 1, 0, 1, 3, 3, 3, 1, 3, 3, 0, 0, 3, 3, 3, 0, 2, 3, 3, 0, 1, 1, 3, 0, 0, 3, 1, 3, 3, 1, 3, 2, 1, 0, 0, 3, 0, 1, 0, 1, 0, 1, 2, 3, 3, 3, 3, 3, 2, 1, 1, 3, 3, 1, 3, 1, 3, 2, 1, 3, 3, 0, 3, 0, 3, 3, 3, 1, 1, 3, 2, 3, 0, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 1, 1, 0, 3, 0, 3, 1, 3, 3, 3, 1, 3, 3, 0, 3, 0, 2, 3, 3, 0, 3, 3, 3, 3, 0, 2, 3, 1, 3, 3, 3, 0, 3, 1, 3, 3, 3, 1, 3, 0, 2, 0, 3, 3, 3, 2, 3, 3, 3, 0, 0, 1, 1, 3, 3, 0, 3, 2, 0, 1, 1, 3, 0, 3, 1, 1, 3, 2, 2, 2, 3, 3, 3, 1, 1, 3, 0, 3, 0, 1, 3, 3, 0, 3, 1, 0, 3, 0, 3, 3, 2, 3, 3, 3, 3, 0, 3, 3, 3, 1, 0, 3, 2, 1, 3, 0, 1, 1, 0, 1, 3, 2, 0, 3, 0, 3, 1, 0, 3, 2, 0, 3, 0, 0, 2, 1, 3, 0, 3, 3, 0, 0, 3, 3, 1, 3, 0, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 1, 1, 3, 3, 0, 3, 3, 3, 1, 0, 1, 3, 0, 1, 3, 0, 3, 3, 3, 0, 3, 3, 0, 1, 3, 3, 1, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3, 1, 0, 3, 3, 0, 3, 3, 1, 0, 3, 1, 1, 3, 3, 3, 2, 3, 0, 0, 3, 3, 3, 3, 3, 3, 2, 1, 3, 3, 0, 3, 0, 1, 3, 1, 3, 3, 1, 1, 1, 1, 3, 3, 3, 1, 3, 0, 3, 3, 3, 2, 3, 1, 3, 3, 1, 1, 3, 3, 3, 0, 0, 3, 3, 0, 3, 3, 0, 0, 3, 3, 3, 3, 3, 1, 1, 0, 3, 3, 3, 3, 0, 3, 1