26 May 2020
•
TIL
구입했던 컴퓨터 비전책을 읽으며 구현해보고 있다.
취업준비 하면서 공부중이라 이전보다 속도가 느려지고 있다. ㅜㅜ
요즘은 기업정보 보다가 하루 다가는거 같다.
프로젝트하는 스터디도 구하고 싶은데 코로나때문인지 구하는 사람들이 많이 안보인다.
TID
- 2020/05/16 - how to read a paper 정리
- 2020/05/17~22 프로그래머를 위한 선형대수 필요한 부분만 읽기
- 2020/05/19 - 코딩테스트 응시
- 2020/05/23 - 프로그래머를 위한 확률과 통계 1장
- 2020/05/23~24 - 선형대수 유튜브 강의(이상엽math) 1강~2강
- 2020/05/23 - 컴퓨터 비전 2-2장
- 2020/05/24 - 자소서 작성
- 2020/05/25 - 컴퓨터 비전 2-2장 정리
- 2020/05/26 - 프로그래머를 위한 확률과 통계 2장-조건부확률까지
- 2020/05/26 - 컴퓨터 비전 2-3장 공부 및 구현
- 2020/05/26 - 선형대수 (이상엽math) 3강
TODO list
26 May 2020
•
Linear Algebra
벡터공간(VectorSpace)
체(field) \(F\)에 대한 가군 \((V,+,\cdot)\) 을 벡터공간, \(V\)의 원소를 벡터라 한다.
이때 \(+\)는 벡터의 덧셈이고, \(\cdot\)는 벡터의 Scalar배이다.
(1) 벡터의 공리
- \((V,+)\)는 아벨군이다 \((u,v,w \in V)\).
- \((u+v)+w=u+(v+w)\).
- \(u+v=v+u\).
- \(u + \vec{0} = u\) 인 \(\vec{0}\)가 \(V\)에 존재한다.
- \(u + ( - u ) = \vec{0}\) 인 \(-u\)가 \(V\)에 존재한다.
- \((V,+,\cdot)\)는 \(F\)의 가군이다. \((k , m \in F)\).
- \(k \cdot (m \cdot u) = (km) \cdot u\).
- \(F\)의 곱셈 항등원 \(1\)에 대해 \(1 \cdot u = u\).
- \((k_m) \cdot (u+v) = k \cdot u + m \cdot u + k \cdot v + m \cdot v\).
(2) 선형 생성(Linear Span)
1) 부분벡터공간
벡터공간 \(V\)상에서 정의된 덧셈과 스칼라배에 대하여 그 자체로서 벡터공간이 되는 \(V\)의 부분집합 \(W\)를 \(V\)의 부분벡터공간 또는 부분공간이라 한다.
2) 선형생성
벡터공간 \(V\)의 공집합이 아닌 부분집합 \(S={v_{1},v_{2}, \ ... \ ,v_{n}}\) 내의 벡터들의 가능한 모든 선형결합으로 이루어진, \(V\)의 부분벡터공간을 \(S\)의 (선형) 생성 \(span(S)\)이라 한다.
즉,
\(span(S) = \begin{Bmatrix} \sum_{i=0}^{m} k_{i}v_{i} | \ k_{i} \in F , v_{i} \in S \end{Bmatrix}\)
이때 \(S\)가 \(span(S)\)을 (선형)생성한다라고 한다.
ex)
\(S=\begin{Bmatrix}(1,0),(0,1)\end{Bmatrix}\).
\(F=\mathbb{R}\).
\(\Rightarrow span(S) = \begin{Bmatrix}k(1,0)+m(0,1) \ | \ k,m \in F \end{Bmatrix}\).
\(= \begin{Bmatrix}k(1,0)+m(0,1) \ | \ k,m \in F \end{Bmatrix}\).
\(= \mathbb{R^{2}}\).
(3) 선형독립(Linear_independent)
다음을 만족 할 때, 벡터 \(a_{1}, ... ,a_{n}\)은 선형독립(일차독립 혹은 독립)이라고 한다.
수 \(u_{1}, ... , u_{n}\)에 대해
\(u_{1}a_{1}+...+u_{n}a_{n}=\mathbf{0}\)라면
‘\(u_{1}=...=u_{n}=0\)’
이를 반대로 생각했을 때 벡터(혹은 수의 집합) \(x_1 \ne x_2\)일 때, \(x_{1}-x_{2} \ne 0\)이 된다. 즉 결과 \(y\)에 대한 유일한 표현이 된다.
조건을 만족 못했을때 \(a{1}, ... ,a{n}\)을 선형종속(일차종속 혹은 종속)이라고 한다.
선형종속이 포함된 사상 \(A\)는 정방행렬이어도 차원을 감소시키는 사상이 된다.
ex)
-
\(S_{1}=\begin{Bmatrix}(1,0),(0,1),(1,1)\end{Bmatrix}\).
\(k_{1}(1,0)+k_{2}(0,1)+k_{3}(1,1)=\vec{0}\).
\(\Rightarrow \left( {k_{1}=k_{2}=k_{3}=0 \\ k_{1}=k_{2}=1 , k_{3}=-1} \right.\).
\(S_{1}\)는 선형종속 집합
-
\(S_{1}=\begin{Bmatrix}(1,0),(0,1)\end{Bmatrix}\).
\(k_{1}(1,0)+k_{2}(0,1)=\vec{0}\).
\(\Rightarrow k_{1}=k_{2}=0\)._
$$S_{2}는 선형독립 집합
(4) 여러 벡터공간
1) 노름공간(Norm space)
노름이 부여된 \(K-\)벡터공간 \((V,\begin{Vmatrix} \cdot \end{Vmatrix})\)
노름이란 \(\forall u, v \in V, \forall k \in K\)에 대해 아래 세 조건을 만족시키는 함수
\(\begin{Vmatrix} \cdot \end{Vmatrix}: V \rightarrow [0, \infin)\)이다. \((K \in \begin{Bmatrix}R,C\end{Bmatrix} )\), 여기서 \(R\)은 실수집합 \(C\)는 복소수집합.
- \(\begin{Vmatrix} kv \end{Vmatrix} = \begin{vmatrix} k \end{vmatrix} \begin{Vmatrix} v \end{Vmatrix}\).
- \(\begin{Vmatrix} u+v \end{Vmatrix} \leqq \begin{Vmatrix} u \end{Vmatrix} + \begin{Vmatrix} v \end{Vmatrix}\).
- \(\begin{Vmatrix} v \end{Vmatrix} = 0 \Leftrightarrow v= \vec{0}\).
2) 내적공간
내적이 부여된 \(K-\)벡터공간 \((V,\left\langle \cdot,\cdot \right\rangle)\).
내적이란 \(\forall u, v, w \in V, \forall k \in K\)에 대해 아래 네 조건을 만족시키는 함수 \(\left\langle \cdot,\cdot \right\rangle : V \times V \rightarrow K\)이다. \((K\in \left\{ {R,C} \right\})\).
- \(\left\langle u+v,w \right\rangle = \left\langle u,w \right\rangle + \left\langle u,w \right\rangle\).
- \(\left\langle ku,v \right\rangle = k\left\langle v,u \right\rangle\).
- \(\left\langle u,v \right\rangle = \left\langle \overline{v,u} \right\rangle\).
- \(v \neq \vec{0} \Rightarrow \left\langle v,v \right\rangle > 0\).
3) 유클리드 공간
음이 아닌 정수 \(n\)에 대하여 \(n\)차원 유클리드공간 \(\mathbb{R^{n}}\)은 실수집합 \(\mathbb{R}\)의 \(n\)번 곱집합이며, 이를 \(n\)차원 실수 벡터공간으로써 정의하기도 한다.
내적 \(\left\langle u,v \right\rangle=\sum_{i=1}^{n}u_{i}v_{i}=u \cdot v\)을 정의하면 점곱, 스칼라곱 이라고도한다.
4) 기저와 차원
1 - 기저
벡터공간 \(V\)의 부분집합 \(B\)가 선형독립이고 \(V\)를 생성할 때, \(B\)를 V의 기저라 한다.
ex)
- \(B_{1} = \left\{ (1,0) ,(0,1)\right\}\).
\(\Rightarrow span(B_{1}) = \mathbb{R^2}\).
따라서 \(B_{1}\)은 \(V\)의 기저이다.
- \(B_{2} = \left\{ (1,0) ,(1,1) \right\}\).
\((a,b) = k(1,0)+m(1,1) = (k+m,m)\)._
\(m=b, k=a-b\).
따라서 \(B_{2}\)는 \(V\)의 기저이다.
- \(S= \left\{ (1,0), (0,1) , (1,1)\right\}\).
\(span(S) = \mathbb{R^2}\).
성립하지만 선형종속이다. 따라서 \(S\)는 \(V\)의 기저가 아니다.
2 - 차원
\(B\)가 벡터공간 \(V\)의 기저일 때 \(B\)의 원소의 개수를 \(V\)의 차원 \(dim(V)\)라 한다.
ex)
- 기저의 예제에서 \(dim(V)=n(B_{1})=n(B_{2})=2\).
3 - 정규기저
다음 조건을 만족하는 노름공간 \(V\)의 기저 \(B\)를 정규기저라 한다.
\(\forall b \in B, \begin{Vmatrix}b\end{Vmatrix}=1\)
4 - 직교기저
다음 조건을 만족하는 내적공간 \(V\)의 기저 \(B\)를 직교기저라 한다.
\(\forall b_{1},b_{2} \in B, \left\langle b_{1},b_{2} \right\rangle =0\)
5 - 정규직교기저
정규기저이자 직교기저인 내적공간의 기저를 정규직교기저라 한다.
특히 \(\mathbb{R^{n}}\)의 정규직교기저 $$\left{ (1,0,…,0),(0,1,…,0),(0,0,…,1) \right}를 표준기저라 한다.
ex)
\(\mathbb{R^2}\)에 대해서
- \(B_{1}= \left\{ (2,0),(1,1) \right\}\) 정규 X, 직교 X
- \(B_{2}= \left\{ (1,0),(\frac{1}{\sqrt{2}},\frac{1}{\sqrt{2}}) \right\}\) 정규 O, 직교 X
- \(B_{3}= \left\{ (1,1),(1,-1) \right\}\) 정규 X, 직교 O
- \(B_{4}= \left\{ (1,0),(0,1) \right\}\) 정규 O, 직교 O
26 May 2020
•
Linear Algebra
대수구조(Algebraic Structure)
대수 : 수를 대신한다.
수 뿐 아니라 수를 대신할 수 있는 모든 것을 대상으로 하는 집합과 그 집합에 부여된 연산이 여러 가지 공리로써 엮인 수학적 대상.
간단히 일련의 연산들이 주어진 집합을 대수구조라고 한다.
여러 대수구조
-
반군 : 집합과 그 위의 결합법칙을 따르는 하나의 이항 연산을 갖춘 대수구조
-
모노이드 : 항등원을 갖는 반군
-
군(group) : 역원을 갖는 모노이드 (집합, 이항연산 하나, 결합법칙, 항등원, 역원을 갖춰야 한다.)
-
아벨군(abelian group,또는 가환군(commutative group)) : 교환법칙이 성립하는 군
-
환(ring) : 덧셈에 대하여 아벨군, 곱셈에 대하여 반군(결합법칙)을 이루고 분배법칙이 성립하는 대수구조
ex) A가 정수 집합, (A, +, x)의 경우 +가 아벨군, x가 반군(정수 공간에서 소수가 포함되지 않아 역원 존재하지 않음) 이때 대수구조 (A, +, x)는 환이다.
-
가군(module) : 어떤 환의 원소에 대한 곱셈이 주어지며, 분배법칙이 성립하는 아벨군
ex) 벡터공간
-
가환환(commutative ring 또는 비가환체(skew field)) : 겁셈이 교환법칙을 만족하는 환
-
나눗셈환(division ring) : 0이 아닌 모든 원소가 역원(=항등원을 가진다)을 가지며, 원소의 개수가 둘 이상인 환
-
체(field) : 가환환인 나눗셈환. 즉, 사칙연산이 자유로이 시행 될 수 있고 산술의 잘 알려진 규칙들을 만족하는 대수구조
ex) 유리수, 실수, 복소수의 연산
범람 채움(Flood_fill)
연결요소의 크기를 어느정도로 하느냐에 따라 다르다.
일반적으로 상하좌우(4-연결성),사각형(8-연결성) 등을 사용한다.
이 크기만큼 연결되어 있을 때 집합 각각을 연결요소(Connected component)라고 한다. 그리고 이 연결요소들에 번호를 부여한 것을 범람 채움(Flood fill)이라고 한다.
코드구현
코드링크
이번에도 레나로 시작한다. OpenCV의 INV flag를 써서 -1을 빼거나 그냥 BINARY flag를 사용해서 X-1로 해도 된다. 이진화 영상에서 나타난 부분을 -1로 하는게 포인트이다.
이번에는 레나사진에서 히스토그램을 추출하고 여기서 값을 정해 이진화를 진행할 것이다.
import numpy as np
import cv2
import matplotlib.pyplot as plt
import queue
img = cv2.imread('./data/lena.jpg',cv2.IMREAD_GRAYSCALE)
ret,thr = cv2.threshold(img,200,255,cv2.THRESH_BINARY_INV)
flood = thr.copy()/255.
flood -=1
plt.imshow(flood.copy()*-1,cmap='gray')
plt.show()
단순 이중 for문을 돌면서 DFS로 탐색하면 재귀 호출이 깊어져 스택 오버플로우가 발생할수 있기 때문에 queue를 이용해서 함수를 작성하고 연결요소마다 번호를 부여한다.
def flood_fill(img,j,i,label):
q = queue.Queue()
q.put((j,i))
while q.qsize():
(y,x)= q.get()
if img[y,x] == -1:
left=right=x
while left-1>0 and img[y,left-1] == -1:
left-=1
while right+1<img.shape[1] and img[y,right+1] ==-1:
right+=1
for z in range(left,right+1):
img[y,z]=label
if y-1>0 and img[y-1,z]==-1 and (z==left or (z-1>0 and img[y-1][z-1] !=-1)):
q.put((y-1,z))
if y+1<img.shape[1] and img[y+1,z]==-1 and (z==left or (z-1>0 and img[y+1][z-1] !=-1)):
q.put((y+1,z))
label = 1
for j in range(img.shape[0]):
for i in range(img.shape[1]):
if(flood[j,i]==-1):
flood_fill(flood,j,i,label)
label+=1
실제 확인을 하고 싶으면 다음과 같이 하면 된다. 연결요소가 너무 짧은 경우 점조차 보기 힘들다.
floos = np.zeros((img.shape[0],img.shape[1],label))
for i in range(1,label+1): # label은 1부터 시작하므로 주의한다.
floos[...,i-1] = flood == i
floos = floos.astype(np.float32)
for i in range(label):
plt.imshow(floos[...,i],cmap='gray')
plt.show()
영상 이진화
어떠한 기준 T로 명암값을 흑과 백 하나로 결정하는 방법이다. 이때 기준 T를 임계값(Threshold)라고 한다.
일반적인 식은 다음과 같다.
\[b(j,i) = \begin{cases}1, f(j,i) \ge T \\ 0, f(j,i) < T \end{cases}\]
이번에는 레나사진에서 히스토그램을 추출하고 여기서 값을 정해 이진화를 진행할 것이다.
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread('./data/lena.jpg',cv2.IMREAD_GRAYSCALE)
hist = np.zeros(256)
img_flat = np.reshape(img,-1)
for i in range(len(img_flat)):
hist[img_flat[i]]+=1
hist = hist/len(img_flat)
fig = plt.figure()
plt.subplot(121)
plt.imshow(img,cmap='gray')
plt.subplot(122)
plt.bar(np.arange(256),hist)
plt.xlim([0,256])
fig.tight_layout()
plt.show()
분포가 최대한 고른 지점은 약 120정도로 보인다. 따라서 120을 임계값으로 이진화를 진행한다.
def Threshold(img, T):
return np.uint8(img>T)*255
bi_img = Threshold(img,120)
plt.imshow(bi_img,cmap='gray')
plt.show()
임계값 방법은 단순한 반면 이렇게 히스토그램을 관찰하여 해야하지만 컴퓨터 비전에서는 이것을 자동화 해야한다. 히스토그램의 봉우리 부분이 뚜렸하지 않을 수록 임계값을 콕 집어내기 애매한 경우가 많다.
오츄알고리즘 (Otsu Algoritm)
오츄가 제안한 알고리즘은 현재도 널리 사용되는 이진화 알고리즘이다. 임계값 \(t\)를 기준으로 화소를 두 집합으로 나누었을 때, 각 집합의 명암 분포가 균일할수록 좋다는 점에 착안해 균일성이 클수록 \(t\)에게 높은 점수를 준다. 균일성은 그룹의 분산으로 측정하고 분산이 작을수록 균일성이 높다. 가능한 \(t\)에 대해 점수를 계산한 후 가장 좋은 \(t\)를 최종 임계값으로 취한다. 일종의 최적화 알고리즘이다.
여기서 목적 함수(Objective function) 혹은 비용 함수(Cost function)은 분산을 사용한다. 분산이 작을수록 균일성이 크므로 목적 함수값이 작을수록 점수가 높다고 거꾸로 생각해야 한다.
식은 다음과같다.
\(T = \underset{t \in \left\{ 0,1,...,L-1 \right\}}{argmin} v_{within}(t) \\
v_{within}(t) = w_{0}(t)v_{0}(t) + w_{1}(t)v_{1}(t) \\
w_{0}(t) = \sum_{i=0}^{t} \hat{h}(i), \qquad w_{1}(t) = \sum_{i=t+1}^{L-1} \hat{h}(i) \\
\mu_{0}(t) = \frac{1}{w_{0}(t)} \sum_{i=0}^{t} i \hat{h}(i), \qquad \mu_{1}(t) = \frac{1}{w_{1}(t)} \sum_{i=t+1}^{L-1} i \hat{h}(i) \\
v_{0}(t) = \frac{1}{w_{0}(t)} \sum_{i=0}^{t} \hat{h}(i)(i-\mu_{0}(t))^{2}, \qquad v_{1}(t) = \frac{1}{w_{1}(t)} \sum_{i=t+1}^{L-1} \hat{h}(i)(i-\mu_{1}(t))^{2}\)
여기서 \(v_{within}(t)\) 가 목적 함수 역할을 하고 두 분산의 가중치 합으로 정의된다. \(w_{0}(t)\)와 \(w_{1}(t)\)는 임계값 \(t\)에 따라 생성된 흑 화소와 백 화소 집합의 크기로서 가중치 역할을 한다. \(v_{0}(t)\)와 \(v_{1}(t)\)는 두 집합의 분산이다. \(\mu\)는 평균값이다.
여기서 두 개의 가중치와 두 개의 분산을 L번 계산해야 하므로 알고리즘의 점근적 시간 복잡도(asymptotic time complexity)는 \(O(L^2)\)이다. 따라서 실시간 처리에서는 부담스럽다.
개선하기 위해 먼저 \(\mu\)와 \(v\)는 주어진 영상에 대해 한 번만 계산하면 되기 떄문에 상수로 간주 가능하다.
\(\mu = \sum_{i=0}^{L-1}i\hat{h}(i), \ v=\sum_{i=0}^{L-1}(i-\mu)^2\hat{h}(i)\)
이 식은 다음과 같이 쓸 수 있다.
\(v = \sum_{i=0}^{t}(i-\mu_{0}(t)+\mu_{0}(t)-\mu)^2\hat{h}(i) + \sum_{i=t+1}^{L-1}(i-\mu_{1}(t)+\mu_{1}(t)-\mu)^2\hat{h}(i) \\
= \sum_{i=0}^{t}((i-\mu_{0}(t))^2 + 2(i-\mu_{0}(t))(\mu_{0}(t)-\mu)+(\mu_{0}(t)-\mu)^2)\hat{h}(i)+ \\ \sum_{i=t+1}^{L-1}((i-\mu_{1}(t))^2+2(i-\mu_{1}(t))(\mu_{1}(t)-\mu)+(\mu_{1}(t)-\mu)^2)\hat{h}(i)\\
= \sum_{i=0}^{t}((i-\mu_{0}(t))^2 + (\mu_{0}(t)-\mu)^2)\hat{h}(i) + \sum_{i=t+1}^{L-1}((i-\mu_{1}(t))^2+(\mu_{1}(t)-\mu)^2)\hat{h}(i)\)
이전 식에 다시 대입하면
\(v=\left\{ w_{0}(t)(\mu_{0}(t)-\mu)^2 + w_{1}(t)(\mu_{1}(t)-\mu)^2\right\} + \sum_{i=0}^{t}(i-\mu_{0}(t))^2\hat{h}(i)+\sum_{i=t+1}^{L-1}(i-\mu_{1}(t))^2\hat{h}(i) \\
= \left\{ w_{0}(t)(\mu_{0}(t)-\mu)^2 + w_{1}(t)(\mu_{1}(t)-\mu)^2\right\} + \left\{ w_{0}(t)v_{0}(t)+w_{1}(t)v_{1}(t)\right\}\)
\(w_{1}(t) = 1- w_{0}(t)\)와 \(\mu = w_{0}(t) \mu_{0}(t) + w_{1}(t) \mu_{1}(t)\)를 대입해 정리하면 (사실 이 부분 이해가 안간다)
\(v = w_{0}(t)(1-w_{0}(t))(\mu_{0}(t)-\mu_{1}(t))^2 + v_{within}(t) = v_{between}(t)+v_{within}(t)\)
\(v\)는 상수 이므로 \(v_{within}(t)\)를 최소화하는 일은 \(v_{between}(t)\)를 최대화하는 것과 똑같다. 따라서 최대화 문제를 다음과 같이 바꿀 수 있다.
\(T = \underset{t \in \left\{ 0,1,...,L-1 \right\}}{argmin} v_{between}(t) \\
where, v_{between}(t) = w_{0}(t)(1-w_{0}(t))(\mu_{0}(t)-\mu_{1}(t))^2\)
전체적인 수식은 다음과 같이 쓸 수 있다.
\(초깃값 (t=0) : w_{0}(0)=\hat{h}(0), \mu_{0}(0)=0 \\
순환식(t>0) : \\
w_{0}(t)=w_{0}(t-1)+\hat{h}(t)\\
\mu_{0}(t) = \frac{w_{0}(t-1)\mu_{0}(t-1)+t\hat{h}(t)}{w_{0}(t)}\\
\mu_{1}(t) = \frac{\mu - w_{0}(t)\mu_{0}(t)}{1-w_{0}(t)}\)
과정 이해가 잘 안가므로 컨셉만 외워야 겠다.
코드 구현
코드링크
위에서 이진화로 이미 정규화된 히스토그램을 그렸다. 위 코드에서 이어서 구현을 한다.
먼저 \(\mu\)를 구하고 나머지 값들을 초기화해 준다.
mu = 0
for i in range(256):
mu+= i*hist[i]
w =np.zeros(256)
mu0 =np.zeros(256)
mu1 =np.zeros(256)
T_bet =0
w[0] = hist[0]
threshold=0
순환식을 따라 T_bet 값이 최대값일때마다 threshold 값을 index값으로 변경한다.
for i in range(1,256):
w[i] = w[i-1]+hist[i]
mu0[i] = (w[i-1]*mu0[i-1]+i*hist[i])/(w[i]+1e-10)
mu1[i] = (mu - w[i]*mu0[i])/(1-w[i]+1e-10)
n_t = w[i]*(1-w[i])*((mu0[i]-mu1[i])**2)
if n_t>T_bet:
T_bet = n_t
threshold = i
이제 이 threshold 값으로 이진화를 진행하면 끝이다.
otsu_img = Threshold(img,threshold)
plt.imshow(otsu_img,cmap='gray')
plt.show()
OpenCV
opencv에서는 cv2.threshold 함수를 사용하면 가능하다. 각 flag마다 method가 살짝씩 다르게 작동한다.
otsu는 따로 작용하는게 아니라 thr6과 같이 입력해야 한다.
ret, thr = cv2.threshold(img,120,255,cv2.THRESH_BINARY)
ret2, thr2 = cv2.threshold(img,120,255,cv2.THRESH_BINARY_INV)
ret3, thr3 = cv2.threshold(img,120,255,cv2.THRESH_TRUNC) # 픽셀값이 임계값 보다 크면 임계값, 작으면 픽셀값 그대로
ret4, thr4 = cv2.threshold(img,120,255,cv2.THRESH_TOZERO) # 픽셀값이 임계값 보다 크면 픽셀 값 그대로, 작으면 0
ret5, thr5 = cv2.threshold(img,120,255,cv2.THRESH_TOZERO_INV) # 픽셀값이 임계값 보다 크면 0, 작으면 픽셀값 그대로
ret6, thr6 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
fig = plt.figure()
plt.subplot(231)
plt.imshow(thr,cmap='gray')
plt.subplot(232)
plt.imshow(thr2,cmap='gray')
plt.subplot(233)
plt.imshow(thr3,cmap='gray')
plt.subplot(234)
plt.imshow(thr4,cmap='gray')
plt.subplot(235)
plt.imshow(thr5,cmap='gray')
plt.subplot(236)
plt.imshow(thr6,cmap='gray')
fig.tight_layout()
plt.show()