Fundo
Declaração do problema
Objetivos
Escopo
Carregando e preparando imagens
Formatação e conversão de imagens
Extração e seleção de recursos
Regressão Logística
Análise Discriminante Linear
K vizinhos mais próximos
Árvores de decisão
Floresta Aleatória
Ingênuo Bayes
Máquina de vetores de suporte
Métricas de Precisão e Desempenho
Comparação de modelos de aprendizado de máquina
Limitações e Desafios
Resumo das Conquistas
Contribuições e Significância
Trabalhos e melhorias futuras
As doenças das plantas representam ameaças significativas à produtividade agrícola, levando a perdas de rendimento e dificuldades económicas para os agricultores. A detecção oportuna e precisa de doenças de plantas é crucial para a implementação de estratégias eficazes de gestão de doenças e para minimizar os danos às culturas. Os métodos manuais tradicionais de diagnóstico de doenças podem ser demorados, subjetivos e propensos a erros. Portanto, a integração de tecnologias, como aprendizado de máquina e processamento de imagens, surgiu como uma abordagem promissora para automatizar e aprimorar a detecção de doenças em plantas.
O objetivo principal deste projeto é desenvolver um Sistema de Detecção de Doenças de Plantas utilizando algoritmos de aprendizado de máquina e técnicas de processamento de imagens. O sistema visa classificar com precisão as folhas das plantas como saudáveis ou doentes através da análise de imagens digitais das folhas. Ao automatizar o processo de detecção, os agricultores e especialistas agrícolas podem identificar e tratar prontamente as doenças das plantas, permitindo intervenções oportunas e otimizando as práticas de gestão das culturas.
Os objetivos principais deste projeto são os seguintes
Desenvolva um sistema robusto e preciso de detecção de doenças de plantas
Implementar algoritmos de aprendizado de máquina para classificação automatizada de folhas de plantas
Utilize técnicas de processamento de imagem para extrair recursos relevantes de imagens de folhas
Avalie o desempenho e a precisão de diferentes modelos de aprendizado de máquina
Fornece uma interface amigável para interação fácil e intuitiva com o sistema
Este projeto centra-se na detecção de doenças de plantas especificamente em folhas de macieira. O conjunto de dados usado para treinar e testar os modelos é obtido do conjunto de dados Plant-Village, que contém imagens de folhas saudáveis de macieira e folhas afetadas por doenças como sarna da macieira, podridão negra e ferrugem da macieira do cedro. classificação de doenças e fornecer uma ferramenta prática para agricultores e profissionais agrícolas identificarem e manejarem doenças de plantas de forma eficaz. O projeto não cobre a detecção de doenças em tempo real no campo ou a integração de dispositivos de hardware para aquisição de imagens
O conjunto de dados usado para este Sistema de Detecção de Doenças de Plantas compreende imagens de folhas de macieira obtidas do conjunto de dados Plant-Village. O conjunto de dados é organizado em quatro categorias principais que representam diferentes classes de condições da folha da macieira Apple___Apple_scab, Apple___Black_rot, Apple___Cedar_apple_rust e Apple___healthy
Apple___Apple_scab: esta categoria contém 630 imagens, com 598 imagens atribuídas para treinamento e 32 imagens para teste
Apple___Black_rot: O conjunto de dados inclui 621 imagens nesta categoria, com 589 imagens alocadas para treinamento e 32 imagens para teste
Apple___Cedar_apple_rust: O conjunto de dados consiste em 275 imagens de folhas afetadas pela ferrugem da macieira, com 261 imagens usadas para treinamento e 14 imagens para teste
Apple___healthy: Esta categoria contém 1645 imagens de folhas de maçã saudáveis. Destas, 1.562 imagens são designadas para treinamento e 83 imagens são reservadas para teste.
As imagens de treinamento são utilizadas para ensinar os modelos de aprendizado de máquina a reconhecer padrões e distinguir entre folhas saudáveis e doentes. As imagens de teste são usadas para avaliar o desempenho e a precisão dos modelos treinados em dados não vistos. Ao aproveitar esse conjunto diversificado de dados, o Sistema de detecção de doenças de plantas visa classificar com precisão as folhas da macieira como saudáveis ou afetadas por doenças como sarna da macieira, podridão negra ou ferrugem da maçã do cedro. A composição do conjunto de dados permite que o sistema aprenda com uma ampla gama de condições foliares e melhore sua capacidade de generalizar e identificar doenças de plantas com precisão
Carregando e preparando imagens
No contexto do projeto de detecção de doenças das folhas da macieira, o primeiro passo é adquirir um conjunto de dados composto por imagens de folhas de macieira afetadas por diferentes doenças. Essas imagens são então carregadas no sistema para torná-las acessíveis para processamento posterior. Além disso, as imagens são preparadas realizando os ajustes necessários, como redimensioná-las para uma resolução consistente, recortar partes desnecessárias ou normalizar a distribuição de cores Formatação e conversão de imagens Depois que as imagens de folhas de maçã forem carregadas, elas precisam ser formatadas e convertidas para garantir compatibilidade com as etapas subsequentes do projeto. Isso envolve padronizar o formato da imagem, convertendo-a em um tipo de arquivo específico, como JPEG ou PNG. Além disso, ajustes podem ser feitos na resolução do espaço de cores ou em outros atributos da imagem para garantir consistência e facilitar análises precisas.
Extração e seleção de recursos
A extração de características é uma etapa crucial na detecção de doenças em folhas de macieira. Várias técnicas são usadas para extrair características relevantes das imagens das folhas. Essas técnicas incluem a análise da textura para capturar padrões texturais associados a doenças, o exame da cor para identificar variações ligadas a doenças específicas e o estudo da forma para detectar irregularidades na morfologia das folhas. Ao extrair essas características distintivas, os algoritmos de aprendizado de máquina subsequentes podem diferenciar efetivamente entre folhas de maçã saudáveis e doentes.
Seleção de recursos
Esta etapa envolve a escolha de um subconjunto dos recursos extraídos com base em sua relevância e poder discriminatório. A seleção de recursos ajuda a reduzir a dimensionalidade do conjunto de dados, eliminando ruídos ou informações redundantes. Ao selecionar os recursos mais informativos, a eficiência e a precisão do modelo de detecção de doenças podem ser melhoradas
O projeto de detecção de doenças nas folhas da macieira utiliza uma variedade de algoritmos de aprendizado de máquina para desenvolver um modelo eficaz de classificação de doenças. Os seguintes algoritmos são empregados
Regressão Logística: A regressão logística é usada para prever a probabilidade de uma folha de maçã ser saudável ou doente com base nas características extraídas
Análise Discriminante Linear: A Análise Discriminante Linear ajuda a classificar as folhas de macieira, encontrando uma combinação linear de características que melhor separa amostras saudáveis e doentes
K Vizinhos Mais Próximos (KNN): K Vizinhos Mais Próximos classifica as folhas de macieira comparando suas características com as dos vizinhos mais próximos no espaço de características
Árvores de decisão: as árvores de decisão usam uma série de condições if-else para classificar amostras com base em seus recursos e relacionamentos hierárquicos.
Random Forest: Random Forest é um método de aprendizagem conjunto que combina múltiplas árvores de decisão para aumentar a precisão da classificação
Naïve Bayes: Naïve Bayes é um algoritmo probabilístico que calcula a probabilidade de uma folha de maçã pertencer a uma determinada classe de doença
Support Vector Machine (SVM): Support Vector Machine constrói hiperplanos em um espaço de recursos de alta dimensão para classificar folhas de maçã
Depois de selecionar os algoritmos de aprendizado de máquina, os modelos são treinados usando um conjunto de dados rotulado que consiste em imagens de folhas de macieira com rótulos de doenças correspondentes. Os modelos aprendem a reconhecer padrões e relações entre características e classes de doenças durante esta fase de treinamento. Para garantir a confiabilidade e generalização dos modelos, é realizado um processo de validação. Os modelos treinados são avaliados usando um conjunto de dados de validação separado que não foi usado durante o treinamento. Isso ajuda a avaliar a capacidade dos modelos de classificar com precisão amostras invisíveis de folhas de macieira
Depois que os modelos são treinados e validados, eles são testados em um conjunto de dados de teste separado que contém imagens novas e inéditas de folhas de macieira. Os modelos prevêem a classe da doença para cada amostra, e métricas de avaliação de desempenho, como exatidão, precisão, recall e pontuação F1, são calculadas para medir a eficácia dos modelos na detecção de doenças.
Em [1]:
# -----------------------------------# EXTRAÇÃO DE RECURSOS GLOBAIS# --------- --------------------------de sklearn.preprocessing import LabelEncoderfrom sklearn.preprocessing import MinMaxScalerimport numpy as npimport mahotasimport cvimport osimport h5py# ----- ---------------# parâmetros ajustáveis# --------------------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 para conversão RGBIn [2]:# Convertendo cada imagem para RGB do formato BGRdef rgb_bgr(image):rgb_img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)return rgb_img##### RGB para HSV (valor de saturação de matiz) ConversionIn [3]:# Conversão para formato de imagem HSV de RGBdef bgr_hsv(rgb_img):hsv_img = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2HSV)return hsv_img##### Image SegmentationIn [4]:# para extração de cor verde e marromdef 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, mask=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, mask=disease_mask)final_mask = healthy_mask + doença_maskfinal_result = cv2.bitwise_and(rgb_img, rgb_img, mask=final_mask)return final_result##### Determinando os descritores de recursos###### 1. Hu MomentsIn [5]:# feature-descriptor-1: Hu Momentsdef fd_hu_moments(image) :image = cv2.cvtColor (imagem, cv2.COLOR_BGR2GRAY) recurso = cv2.HuMoments(cv2.moments(image)).flatten()return feature###### 2. Haralick TexturesIn [6]:# feature-descriptor-2: Haralick Texturedef fd_haralick(image):gray = cv2.cvtColor (imagem, cv2.COLOR_BGR2GRAY)haralick = mahotas.features.haralick(cinza).mean(axis= 0 )return haralick###### 3. Color HIstogramIn [7]:# feature-descriptor-3: Color Histogramdef fd_histogram(image, mask=None):image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)hist = cv2 .calcHist( [imagem], [ 0 , 1 , 2 ], Nenhum, [caixas, caixas, caixas], [ 0 , 256 , 0 , 256 , 0 ,256 ] )cv2.normalize(hist, hist)return hist.flatten()##### Carregando o conjunto de dados de treinamentoEm [8]:# obtenha os rótulos de treinamentotrain_labels = os.listdir(train_path)# classifique os rótulos de treinamentotrain_labels.sort() print(train_labels)# listas vazias para conter vetores de recursos e rótulosglobal_features = []labels = [] ['Apple___Apple_scab', 'Apple___Black_rot', 'Apple___Cedar_apple_rust','Apple___healthy']##### Gerando os recursos e incorporações de rótulos a partir do conjunto de dadosEm [9]:# faça um loop nas subpastas de dados de treinamento para training_name em train_labels:# join o caminho dos dados de treinamento e a pasta de treinamento de cada espécieimg_dir_path = os.path.join(train_path, training_name)# obtém o rótulo de treinamento atualcurrent_label = training_name# faz um loop sobre as imagens em cada subpastafor img in os.listdir(img_dir_path):# obtém o nome do arquivo de imagemfile = os.path.join( img_dir_path, img)# leia a imagem e redimensione-a para uma imagem de tamanho fixo = cv2.imread(file)image = cv2.resize(image, fixo_size)# Função em execução bit por BitRGB_BGR = rgb_bgr(image)BGR_HSV = bgr_hsv(RGB_BGR)IMG_SEGMENT = img_segmentation(RGB_BGR, BGR_HSV)# Chamada para descritores de recursos globaisfv_hu_moments = fd_hu_moments(IMG_SEGMENT)fv_haralick = fd_haralick(IMG_SEGMENT)fv_histogram = fd_histogram(IMG_SEGMENT)# Concatenar recursos globaisglobal_feature = np.hstack([fv_histogram, fv_haralick,fv_hu_moments])# atualizar a lista de rótulos e recursos vectorslabels.append(current_label)global_features.append(global_feature)print("[STATUS] pasta processada: {}".format(current_label))print("[STATUS] extração de recurso global concluída...") [STATUS] pasta processada: Apple___Apple_scab[STATUS] pasta processada: Apple___Black_rot[STATUS] pasta processada: Apple___Cedar_apple_rust[STATUS] pasta processada: Apple___healthy[STATUS] concluída Extração de recurso global...In [10]:# print(global_features)In [ 41]:# obtém o vetor de recurso geral sizeprint("[STATUS] vetor de recurso tamanho{}".formato(np.array(global_features).shape)) [STATUS] tamanho do vetor de recurso (3010, 532)Em [12]:# obtenha o tamanho geral do rótulo de treinamento# print(labels)print("[STATUS] training Labels {}".format(np.array(labels).shape )) [STATUS] rótulos de treinamento (3010,)
Valor codificado do rótuloApple___Apple_scab 0Apple___Black_rot 1Apple___Cedar_apple_rust 2Apple___healthy 3
Em [13]:
targetNames = np.unique(labels) le = LabelEncoder() target = le.fit_transform(labels) print(targetNames) print("[STATUS] rótulos de treinamento codificados...") ['Apple___Apple_scab' 'Apple___Black_rot' 'Apple___Cedar_apple_rust' ' Apple___healthy'] [STATUS] rótulos de treinamento codificados...
Em [14]:
from sklearn.preprocessing import MinMaxScalerscaler = MinMaxScaler(feature_range=( 0 , 1 ))rescaled_features = scaler.fit_transform(global_features)print("[STATUS] vetor de recurso normalizado...")rescaled_features[STATUS] vetor de recurso normalizado...Out [14]:matriz([[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] rótulos de destino: {}".format(target))print("[STATUS] formato dos rótulos de destino: {}".formato(target.shape)) [STATUS] rótulos de destino: [0 0 0 ... 3 3 3] [STATUS] formato dos rótulos de destino: (3010,)
um. Características
h5f_data = h5py.File(h5_train_features, "w")h5f_data.create_dataset("dataset_1", data=np.array(rescaled_features))Out[16]:<conjunto de dados HDF5 "dataset_1": forma (3010, 532), tipo " <f8">
h5f_label = h5py.File(h5_train_labels, "w")h5f_label.create_dataset("dataset_1", data=np.array(target))Out[17]:<conjunto de dados HDF5 "dataset_1": shape (3010,), digite "< i8">In [43]:h5f_data.close()h5f_label.close()
# -----------------------------------# TREINANDO NOSSO MODELO# --------- --------------------------importar h5pyimport numpy como npimport osimport cvimport warningsfrom matplotlib importar pyplotfrom sklearn.model_selection importar train_test_split, cross_val_scorefrom sklearn.model_selection importar KFold, StratifiedKFoldfrom sklearn.metrics importa confusão_matrix, precisão_score, relatório de classificação de sklearn.linear_model importar LogisticRegression de sklearn.tree importar DecisionTreeClassifier de sklearn.ensemble importar RandomForestClassifier de sklearn.neighbors importar KNeighboursClassifier de sklearn.discriminant_análise importar LinearDiscriminantAnalysisfrom sklearn.naive_bayes importar GaussianNBfrom sklearn.svm import SVCimport joblibwarnings.filterwarnings("ignore")# --------------------# parâmetros ajustáveis# ----------- ---------num_trees = 100test_size = 0.seed = 9scoring = "accuracy"# obtenha os rótulos de treinamentotrain_labels = os.listdir(train_path)# classifique os rótulos de treinamentotrain_labels.sort()se não os.path.exists(test_path):os.makedirs(test_path)# crie todos os modelos de aprendizado de máquinamodels = []models.append(("LR", LogisticRegression(random_state=seed)))models.append(("LDA" , LinearDiscriminantAnalysis()))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)))# variáveis para armazenar os resultados e nomesresultados = []nomes = []# importe o recurso vetor e rótulos treinadosh5f_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()# verifique a forma do vetor de recursos e rótulosprint("[STATUS] apresenta forma: {}".format(global_features. shape))print("[STATUS] formato dos rótulos: {}".format(global_labels.shape))print("[STATUS] treinamento iniciado...")print(global_labels, len(global_labels), len(global_features)) [STATUS] apresenta formato: (3010, 532) Formato dos rótulos [STATUS]: (3010,) [STATUS] treinamento iniciado... [0 0 0 ... 3 3 3] 3010 3010
Em [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] dados de trem e teste divididos...")print("Dados de trem: {} ".format(trainDataGlobal.shape))print("Dados de teste: {}".format(testDataGlobal.shape)) [STATUS] dados de trem e teste divididos... Dados de trem: (2408, 532) Dados de teste: (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]])
Em [22]:
para nome, modelo em modelos: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)" % (nome, 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)
Em [23]:
fig = pyplot.figure()fig.suptitle("Comparação de algoritmo de aprendizado de máquina")ax = fig.add_subplot( 111 )pyplot.boxplot(resultados)ax.set_xticklabels(nomes)pyplot.show()
A partir do resultado acima, podemos ver que o modelo Random Forest Classifier tem a maior precisão de 96,7% e o modelo Gaussian NB tem a menor precisão de 83,9%
<div class="highlight destaque-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content=" Em [24]: clf = RandomForestClassifier(n_estimators=num_trees, random_state= semente) Em [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