sol Espaço cor contorno
Objetivos da aula:
- Conhecer o espaço de cor HSV
- Conhecer o processo de mascara
- conhecer o processo de detecção de contornos
- conhecer o processo de calculo do centro de massa
- conhecer o processo para desenar e escrever na imagem
Espaço de cor HSV¶
Até o momento trabalhamos com imagens em escala de cinza, BGR, RGB e binaria. Agora vamos conhecer e trabalhar com HSV ou HSB.
H - hue (matriz)
S - saturation (saturação)
V - value (Value) ou B - brightness (brilho)
Utilizar esse espaço possui algumas vantagens vamos ver no exemplo abaixo.
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/HSV_colorspace.jpg /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/bolinha.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/convolution.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/formas.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/formas_contorno.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/formas_contornor.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/lena.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/melancia.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/melancia_filtrada.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/HSV_colorspace.jpg /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/melancia_filtrada_rgb.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/same_padding_no_strides.gif /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/sudoku.png /content
!wget https://raw.githubusercontent.com/arnaldojr/cognitivecomputing/master/material/aulas/PDI/lab05/tux.png /content
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('hsv_colorspace.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.figure(figsize = (20,20))
plt.imshow(img); plt.show()
A matiz descreve o pigmento de uma cor e é medido em graus de 0 a 359 graus.
A saturação descreve a vivacidade ou o esmaecimento de uma cor e é medida em porcentagem de 0 a 100 (0 = cor "diluida" 100 = cor pura).
O brilho determina a intensidade percebida (0 = preto 100 = brilho maximo);
Dica: Pra entender bem o que é cada componente, da uma olhada neste link ou digita no google "colorpicker"
lembrete super importante!! a OpenCV trabalha com valores de 8bits (0-255), ou seja o valor da matriz tem que ser divido por 2
Conversão para HSV
Na OpenCV a conversão de BGR para RGB é muito simples, podemos converter diretamete da imagem em BGR usando o cv2.COLOR_BGR2HSV.
img = cv2.imread('bolinha.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.subplot(1, 2, 2)
plt.imshow(img_hsv)
plt.show()
mascara de cor
Para realizar uma marcara de cor, nos usamos a função cv2.inrange para escolher o intervalo de cor ( o valor minimo e o valor maximo).
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('bolinha.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Definição dos valores minimo e max da mascara
# o magenta tem h=300 mais ou menos ou 150 para a OpenCV
image_lower_hsv = np.array([140, 100, 40])
image_upper_hsv = np.array([175, 255, 255])
mask_hsv = cv2.inRange(img_hsv, image_lower_hsv, image_upper_hsv)
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.subplot(1, 2, 2)
plt.imshow(mask_hsv, cmap="Greys_r", vmin=0, vmax=255)
plt.show()
DESAFIO 1¶
Faça a segmentação da meia lua da imagem "melancia.png". O seu resultado deve ser proximo/parecido com a imagem "melancia_filtrada.png".
Dica: talvez você precise usar mais que uma faixa de valores, se necessário use a função "cv2.bitwise_or" para juntar as partes.
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('melancia.png')
img_res = cv2.imread('melancia_filtrada.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_res_rgb = cv2.cvtColor(img_res, cv2.COLOR_BGR2RGB)
fig = plt.figure(figsize=(20,20))
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.subplot(1, 2, 2)
plt.imshow(img_res_rgb)
plt.show()
#Implemente seu código
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('melancia.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
print(img_hsv.shape)
print(img_hsv[220,700])
# Definição dos valores minimo e max da mascara para a melancia
# use o color picker para pegar os valores aproximados de h, s e v para a melancia
melancia_lower1 = np.array([175, 120, 150])
melancia_upper1 = np.array([180, 255, 255])
melancia_lower2 = np.array([0, 140, 160])
melancia_upper2 = np.array([15, 255, 255])
# Cria as máscaras para as faixas de vermelho
mask_1 = cv2.inRange(img_hsv, melancia_lower1, melancia_upper1)
mask_2 = cv2.inRange(img_hsv, melancia_lower2, melancia_upper2)
# Combina as duas máscaras com uma operação bitwise_or
mask_melancia = cv2.bitwise_or(mask_1, mask_2)
plt.subplot(2, 2, 1)
plt.imshow(img_rgb)
plt.subplot(2, 2, 2)
plt.imshow(mask_melancia, cmap="Greys_r")
plt.subplot(2, 2, 3)
plt.imshow(mask_1, cmap="Greys_r")
plt.subplot(2, 2, 4)
plt.imshow(mask_2, cmap="Greys_r")
plt.show()
# Com um pouco mais de trabalho, podemos melhorar um pouco mais a máscara
# mas ja está bom para o que precisamos, e é um bom exemplo de como podemos
# manipular as máscaras para obter o resultado desejado
(723, 1280, 3) [179 172 231]
DESAFIO 2¶
Usando a imagem "melancia_filtrada.png", devolva a cor original que era antes da filtragem.
Dica: Use as funções de "cv2.bitwise_and" para juntar.
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('melancia_filtrada.png')
img_res = cv2.imread('melancia_filtrada_rgb.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_res_rgb = cv2.cvtColor(img_res, cv2.COLOR_BGR2RGB)
fig = plt.figure(figsize=(20,20))
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.subplot(1, 2, 2)
plt.imshow(img_res_rgb)
plt.show()
#Implemente seu código
# Aplica a máscara combinada à imagem original
img_red = cv2.bitwise_and(img_rgb, img_rgb, mask=mask_melancia)
# Mostra a imagem
plt.imshow(img_red)
plt.show()
DETECÇÃO DE CONTORNOS¶
Para realizar a detecção dos contornos, ou bordas de um objeto, usamos a função cv2.findontours
#recarregando o nosso exemplo...
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('bolinha.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Definição dos valores minimo e max da mascara
# o magenta tem h=300 mais ou menos ou 150 para a OpenCV
image_lower_hsv = np.array([140, 50, 100])
image_upper_hsv = np.array([170, 255, 255])
mask_hsv = cv2.inRange(img_hsv, image_lower_hsv, image_upper_hsv)
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.subplot(1, 2, 2)
plt.imshow(mask_hsv, cmap="Greys_r", vmin=0, vmax=255)
plt.show()
# realizando o contorno da imagem
contornos, _ = cv2.findContours(mask_hsv, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# para desenhar o contorno primeiro faz uma copia da imagem
mask_rgb = cv2.cvtColor(mask_hsv, cv2.COLOR_GRAY2RGB)
contornos_img = mask_rgb.copy() # Cópia da máscara para ser desenhada "por cima"
cv2.drawContours(contornos_img, contornos, -1, [255, 0, 0], 5);
plt.figure(figsize=(8,6))
plt.imshow(contornos_img);
# para desenhar o contorno primeiro faz uma copia da imagem
mask_rgb = cv2.cvtColor(mask_hsv, cv2.COLOR_GRAY2RGB)
contornos_img = mask_rgb.copy() # Cópia da máscara para ser desenhada "por cima"
cv2.drawContours(img_rgb, contornos, -1, [0, 0, 255], 10);
plt.figure(figsize=(8,6))
plt.imshow(img_rgb);
Note que a função findContours devolve uma lista com os contornos detectados.
print("Quantidade de contornos encontrado: ", len(contornos))
Quantidade de contornos encontrado: 1
DESAFIO 3¶
Usando a imagem "formas.png", faça um código que detecta todos os contornos da imagem. O resultado deve ser parecido com "formas_contorno.png"
Dica: Neste desafio, basicamente tem que ajustar a a mascara, o resto não muda.
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('formas.png')
img_res = cv2.imread('formas_contorno.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_res_rgb = cv2.cvtColor(img_res, cv2.COLOR_BGR2RGB)
# Definição dos valores minimo e max da mascara
image_lower_hsv = np.array([0, 1, 0])
image_upper_hsv = np.array([180, 255, 255])
fig = plt.figure(figsize=(20,20))
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.subplot(1, 2, 2)
plt.imshow(img_res_rgb)
plt.show()
#Implemente seu código
# para desenhar o contorno podemos fazer uma imagem em escala de cinza e depois desenhar o contorno
# ou podemos usar a imagem original e desenhar o contorno por cima
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('formas.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Define as faixas de cor para o vermelho no espaço HSV
lower_hsv = np.array([0, 1, 1])
upper_hsv = np.array([180, 255, 255])
# Cria as máscaras
edges = cv2.inRange(img_hsv, lower_hsv, upper_hsv)
# Encontra os contornos na imagem
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Desenha os contornos na imagem em escala de cinza
edges_copy = edges.copy()
img_gray_contours = cv2.cvtColor(edges_copy, cv2.COLOR_GRAY2RGB)
cv2.drawContours(img_gray_contours, contours, -1, (255, 0, 0), 5)
# Desenha os contornos na imagem original
img_contours = img_rgb.copy()
cv2.drawContours(img_contours, contours, -1, (255, 0, 0), 2)
# Exibe a imagem original e a imagem com os contornos
plt.figure(figsize=(10, 5))
plt.subplot(1, 3, 1)
plt.imshow(img_rgb)
plt.title('Imagem Original')
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(img_gray_contours)
plt.title('Contornos gray')
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(img_contours)
plt.title('Contornos RGB')
plt.axis('off')
plt.show()
DESAFIO 4¶
Altere o seu codigo para desenhar o contorno de maior area da imagem. Use a função "cv2.contourArea()".
Referência da documentação: https://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html
Dica: Use um for para varrer a lista e armazene o indice do maior valor e passe esse valor para desenhar o contorno.
## Implemente seu código
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('formas.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Define as faixas de cor para o vermelho no espaço HSV
lower_hsv = np.array([0, 1, 1])
upper_hsv = np.array([180, 255, 255])
# Cria as máscaras
edges = cv2.inRange(img_hsv, lower_hsv, upper_hsv)
# Encontra os contornos na imagem
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
### o findContours devolve uma lista de contornos, a ideia é varrer esses contornos e encontrar a maior area e maior index
max_area = 0
max_index = -1
for i, contour in enumerate(contours):
area = cv2.contourArea(contour)
if area > max_area:
max_area = area
max_index = i
print(f'Foram detectados {len(contours)} contornos.\nO maior contorno é o de indice {max_index} com a área de {max_area}')
# Desenha o contorno de maior área na imagem original
img_contour_max = img_rgb.copy()
if max_index != -1:
cv2.drawContours(img_contour_max, [contours[max_index]], -1, (0, 0, 0), 10)
# Exibe a imagem original e a imagem com o contorno de maior área
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.imshow(img_contour_max)
plt.title('Maior Contorno')
plt.axis('off')
plt.show()
Foram detectados 11 contornos. O maior contorno é o de indice 1 com a área de 42784.0
CENTRO DE MASSA DE UM OBJETO¶
O calculo para o centro de massa é feito atráves da função cv2.findontours
cv2.__version__
'4.5.5'
#recarregando o nosso exemplo...
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('bolinha.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Definição dos valores minimo e max da mascara
# o magenta tem h=300 mais ou menos ou 150 para a OpenCV
image_lower_hsv = np.array([140, 50, 100])
image_upper_hsv = np.array([170, 255, 255])
mask_hsv = cv2.inRange(img_hsv, image_lower_hsv, image_upper_hsv)
contornos, _ = cv2.findContours(mask_hsv, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
mask_rgb = cv2.cvtColor(mask_hsv, cv2.COLOR_GRAY2RGB)
contornos_img = mask_rgb.copy() # Cópia da máscara para ser desenhada "por cima"
cv2.drawContours(contornos_img, contornos, -1, [255, 0, 0], 5);
plt.figure(figsize=(8,6))
plt.imshow(contornos_img);
# usando o exemplo da documentação https://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html
# notamos que a função devolve um dicionario.
cnt = contornos[0]
M = cv2.moments(cnt)
print( M )
{'m00': 20954.5, 'm10': 8414785.5, 'm01': 4813317.5, 'm20': 3414210172.083333, 'm11': 1932427931.625, 'm02': 1140509199.0833333, 'm30': 1399201096317.6501, 'm21': 783868070420.7833, 'm12': 457787216119.7167, 'm03': 278003040454.85004, 'mu20': 35049847.99971104, 'mu11': -475946.1051416397, 'mu02': 34874354.26211333, 'mu30': -7672941.9208984375, 'mu21': -4968840.380795479, 'mu12': 6858123.621509552, 'mu03': 2822544.575317383, 'nu20': 0.07982364109512664, 'nu11': -0.0010839348312655414, 'nu02': 0.07942396606302501, 'nu30': -0.00012071706132489804, 'nu21': -7.817390189392743e-05, 'nu12': 0.00010789766667418762, 'nu03': 4.4406603113053444e-05}
# Calculo das coordenadas do centro de massa
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
print("centro de massa na possição: ",cx, cy)
centro de massa na possição: 401 229
Vamos plotar isso na imagem para saber se esta correto. A função "cv2.line" vai nos ajudar a desenhar uma cruz. e função "cv2.putText" a escrever na imagem as coordenadas.
## para desenhar a cruz vamos passar a cor e o tamanho em pixel
size = 20
color = (128,128,0)
cv2.line(contornos_img,(cx - size,cy),(cx + size,cy),color,5)
cv2.line(contornos_img,(cx,cy - size),(cx, cy + size),color,5)
# Para escrever vamos definir uma fonte
font = cv2.FONT_HERSHEY_SIMPLEX
text = cy , cx
origem = (0,50)
cv2.putText(contornos_img, str(text), origem, font,1,(200,50,0),2,cv2.LINE_AA)
plt.imshow(contornos_img);
DESAFIO 5¶
O desafio é juntar o que aprendemos em um video, use como base "webcam.py". Você deve seguimentar a cor de um objeto, encontrar seu contorno e montar a imagem segmentada com o centro de massa e suas coordenadas. Video de referência "segmenta_melancia.mp4"
#### seu código aqui...
## Vamos fazer por partes, primeiro vamos achar o centro de massa para a imagem que estamos utilizando da melancia
%matplotlib inline
import cv2
from matplotlib import pyplot as plt
import numpy as np
img = cv2.imread('melancia.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
print(img_hsv.shape)
print(img_hsv[220,700])
# Definição dos valores minimo e max da mascara para a melancia
# use o color picker para pegar os valores aproximados de h, s e v para a melancia
melancia_lower1 = np.array([175, 120, 150])
melancia_upper1 = np.array([180, 255, 255])
melancia_lower2 = np.array([0, 140, 160])
melancia_upper2 = np.array([15, 255, 255])
# Cria as máscaras para as faixas de vermelho
mask_1 = cv2.inRange(img_hsv, melancia_lower1, melancia_upper1)
mask_2 = cv2.inRange(img_hsv, melancia_lower2, melancia_upper2)
# Combina as duas máscaras com uma operação bitwise_or
mask_melancia = cv2.bitwise_or(mask_1, mask_2)
# Encontra os contornos na imagem
contours, _ = cv2.findContours(mask_melancia, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
### o findContours devolve uma lista de contornos, a ideia é varrer esses contornos e encontrar a maior area e maior index
max_area = 0
max_index = -1
for i, contour in enumerate(contours):
area = cv2.contourArea(contour)
if area > max_area:
max_area = area
max_index = i
print(f'Foram detectados {len(contours)} contornos.\nO maior contorno é o de indice {max_index} com a área de {max_area}')
## para desenhar a cruz vamos passar a cor e o tamanho em pixel
size = 20
color = (128,128,0)
# Desenha o contorno de maior área na imagem original
img_contour_max = img_rgb.copy()
if max_index != -1:
cv2.drawContours(img_contour_max, [contours[max_index]], -1, (0, 255, 0), 10)
# Calcula o centro de massa
M = cv2.moments(contours[max_index])
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
# Desenha o centro de massa
cv2.line(img_contour_max,(cx - size,cy),(cx + size,cy),color,5)
cv2.line(img_contour_max,(cx,cy - size),(cx, cy + size),color,5)
cv2.putText(img, f'({cy}, {cx})', (0, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (200, 50, 0), 2, cv2.LINE_AA)
# Exibe a imagem original e a imagem com o contorno de maior área
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.imshow(img_contour_max)
plt.title('Achei você!')
plt.axis('off')
plt.show()
(723, 1280, 3) [179 172 231] Foram detectados 116 contornos. O maior contorno é o de indice 98 com a área de 53061.0
import cv2
import numpy as np
# Inicializa a captura de vídeo da webcam
cap = cv2.VideoCapture(0)
# Definição dos valores mínimo e máximo da máscara para a melancia
melancia_lower1 = np.array([175, 120, 150])
melancia_upper1 = np.array([180, 255, 255])
melancia_lower2 = np.array([0, 140, 160])
melancia_upper2 = np.array([15, 255, 255])
# Loop para ler os frames da webcam
while True:
ret, img = cap.read()
if not ret:
break
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Cria as máscaras para as faixas de vermelho
mask_1 = cv2.inRange(img_hsv, melancia_lower1, melancia_upper1)
mask_2 = cv2.inRange(img_hsv, melancia_lower2, melancia_upper2)
# Combina as duas máscaras com uma operação bitwise_or
mask_melancia = cv2.bitwise_or(mask_1, mask_2)
# Encontra os contornos na imagem
contours, _ = cv2.findContours(mask_melancia, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Encontra o contorno de maior área
max_area = 0
max_index = -1
for i, contour in enumerate(contours):
area = cv2.contourArea(contour)
if area > max_area:
max_area = area
max_index = i
# Desenha o contorno de maior área na imagem original
if max_index != -1:
cv2.drawContours(img, [contours[max_index]], -1, (0, 255, 0), 10)
# Calcula o centro de massa
M = cv2.moments(contours[max_index])
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
# Desenha o centro de massa
cv2.line(img, (cx - 20, cy), (cx + 20, cy), (128, 128, 0), 5)
cv2.line(img, (cx, cy - 20), (cx, cy + 20), (128, 128, 0), 5)
cv2.putText(img, f'({cy}, {cx})', (0, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (200, 50, 0), 2, cv2.LINE_AA)
# Exibe a imagem com o contorno de maior área
cv2.imshow('Achei você!', img)
# Sai do loop se a tecla 'q' for pressionada
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Libera a captura e fecha todas as janelas
cap.release()
cv2.destroyAllWindows()