딥러닝을 활용하여 자동차 번호판을 추출하고 인식(OCR)을 테스트 해 보았을 때 

추출은 잘 되지만 인식은 잘 되지 않는 경우가 많았다. 

그래서 어떻게 하면 더 간단하게 이미지를 변형할 수 있을까 생각을 하며 테스트를 해보았다

    

1. 추출된 번호판에 대해서 진행한다 

2. 번호판 사이 간격이 존재한다는 가정 

3. 번호판을 하나의 단어만을 유추 할 수 있도록 쪼개보자 

 

단순 테스트용으로 내용은 부실합니다 .ㅎㅎ 

 

 순서 

1. 이미지를 gray scale로 변환 

2. Blur를 적용하여 불필요한 엣지를 줄임 

3. 엣지를 찾고 

4. 외곽선을 검출하고 

5. 외곽선 결과를 4개의 점으로 받아 넓이가 큰 순서로 정렬

6. 가장큰 외곽선을 엣지추출한 이미지에서 추출

7. Blur와 closing을 통해   이미지에 노이즈를 줄임 

8. x, y 축 각각 좌표별 원소의 합을 구해 정보가 적은 영역을 찾기 

9. 정보가 적은 영역 제거

10. 다시 x,y축 각각 좌표별 원소의 합을 구해서 plot 해보기 

 

사용한 차량 번호는 없는 번호입니다 ~ 

 

1 ~ 2 - 이미지 전처리 

img = cv2.imread("plate1.jpg")
img2 = img.copy()
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.GaussianBlur(img, (5,5),0)

 

3. edge 추출

edge = cv2.Canny(img, 100,100)

4 ~ 5 외곽선 검출 하고 넓이로 정렬

contours, _ = cv2.findContours(edge.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

sorted_contours = sorted(contours, key=cv2.contourArea, reverse=True)

6. 가장 큰 외곽선 선택 하여 해당 영역만 추출

contour = sorted_contours[0]
xmin = contour[:][:,:,0].min()
ymin = contour[:][:,:,1].min()
xmax = contour[:][:,:,0].max()
ymax = contour[:][:,:,1].max()

plate = edge[ymin:ymax, xmin:xmax]

7. 노이즈 제거를 위해 blur 적용 후 closing

plate = cv2.GaussianBlur(plate, (5,5),0)
kernel =  cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
plate = cv2.morphologyEx(plate, cv2.MORPH_CLOSE, kernel)

8. x축, y축 원소의 개수 확인  

h,w = plate.shape
# x축, y축 각각 원소의 개수 카운팅
x_result = np.sum(plate,axis=0) // 255 
x_result = x_result[5:-5]
y_result = np.sum(plate,axis=1) // 255
y_result = y_result[5:-5]

plt.plot(x_result[5:-5] )
plt.plot(y_result[5:-5] )
plt.xlim([0,w])

노란선이 y축 파란선이 x 축

plot을 보면 이미지에서 아무 정보가 없는 부분의 원소의 합이 0에 가깝게 나온다는 것을 확인 할 수 있다. 

그렇다면 처음 ~ 0까지 정보와 맨뒤에서 0이 아닌 원소에 대해서찾는 다면 원하는 번호만 존재하는 영역을 뽑을 수 있겠다 

 

9. 정보가 적은 영역 제거 후 다시 원소의 개수 확인 

# y축에 대해서 값이 너무 작은 영역 제외
se_list = []
condition = y_result < (y_result.max() // 2)
for i, l in enumerate(condition):
    if l == False :
        se_list.append(i)
        break
for i, l in enumerate(condition[::-1]):
    if l == False :
        se_list.append(h - i)
        break

       
# 다시 x축 y축 각각 원소의 개수 확인
plate = plate[se_list[0] : se_list[1]]
x_result = np.sum(plate,axis=0) // 255
y_result = np.sum(plate,axis=1) // 255
plt.plot(x_result[5:-5] )
plt.plot(y_result[5:-5] )
plt.xlim([0,w])
cv2.imshow("qwe", plate)
plt.show()    

그래프에서 노란선은 y축에 대한 원소의 합 파란선은 x축에 대한 원소의 합이다. 

먼저 x 축에 대하여 보면

3의 정보가 있는 영역은 x축 0 ~ 50까지라고 볼 수 있다

8의 정보가 있는 영역은 x축 50 ~ 120까지라고 볼 수 있다 

이런식으로 각 7자리 영역에 대해서 유추를 해볼 수 있다. 

 

워낙 테스트용으로 만들어본 내용이라 실제 사용은 불가능 하다 

1. 외곽선 검출에서 가장 넓은 영역이 번호판이 아닐 수 있다.

2. 차량 번호판은 다양하기 때문에 각 작업에 다른 처리가 들어가야한다. 

3. 번호판이 기울어져 있다면 x 축에대해서 원소의 개수가없는 영역이 줄어들 것이다. 

 

 

(내용 추가)  

floodFill을 활용한 정보 부곽   

h,w = plate.shape
mask = np.zeros((h+2,w+2), np.uint8)
cv2.floodFill(plate, mask, (0,0), 255, 254)
plate = cv2.bitwise_not(plate)

번호판에서 0,0 위치부터 0에 해당하는 값을255로 변경하고 bitwise not으로 색상을 반전시켰다 

 

1. Image 결합 

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <array>

using namespace std;

int main() {
	// cv namespace는 설명을 위해 사용하지 않음 
	cv::Mat img = cv::imread("img1.png");
	cv::Mat gray_img;
	cv::cvtColor(img, gray_img, cv::COLOR_BGR2GRAY); // gray.로 변환 

	cout << gray_img.size() << " channel : " << gray_img.channels() << endl; // 225x225x1
	cout << img.size() << " channel : " << img.channels() << endl; // 225x225x3

	array<cv::Mat, 3> arr = { gray_img, gray_img, gray_img };

	cv::Mat merge_img; 
	cv::merge(arr, merge_img); // ArrayOfArray를 합침 
	
	cout << merge_img.size() << " channel : " << merge_img.channels() << endl; //225x225x3

	cv::Mat concat_img;
	cv::hconcat(merge_img, img, concat_img); // 수평으로 결합 - 차원이 같아야함 
	//cv::vconcat(merge_img, img, concat_img); // 수직으로 결합 - 차원이 같아야함 

	cout << concat_img.size() << " channel : " << concat_img.channels() << endl; // 450x225x3

	cv::imshow("qwe", concat_img); 
	cv::waitKey(0);

	return 0;
}

  • Opencv에서 제공하는 concat 함수를 사용하여 이미지를 수직 또는 수평으로 결합할 수 있다 
    - vconcat, hconcat 
  • 흑백이미지와 color 이미지를 결합하기 위해서는 흑백이미지의 경우 채널이 1이기 때문에 먼저 3채널로 만든 뒤 결합하여야 한다. 그때 cv::merge 함수를 사용하여 생성하였다  
  • merge를 사용할 때 흑백 이미지에 대해서 array를 생성한 후 merge 하였는데, vertor를 사용하지 않고 array를 사용한 이유는 우리가 원하는 차원의 개수(3)를 알고있기 때문에 원소의 개수를 지정할 수 있는 array를 사용했다 

2. 외곽선 검출 (https://docs.opencv.org/3.4/df/d0d/tutorial_find_contours.html

#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <vector>
#include <array>
#include <iostream>

using namespace std; 

cv::Mat gray_img;
const int max_thresh = 255; // threshold 최댓값 
int thresh = 100;
cv::RNG rng(54321); // Random Number Generator - 랜덤값 생성

void thresh_callback(int, void*);

int main() {
	const char* img_path = "milk.jpg";
	cv::Mat img = cv::imread(img_path); // 이미지 읽어오기

	cv::cvtColor(img, gray_img, cv::COLOR_BGR2GRAY); // gray scale로 변환 

	cv::blur(gray_img, gray_img, cv::Size(3, 3)); // 이미지에 블러 적용 3x3 커널사용

	const char* window_name = "origin"; // 보여줄 윈도우 이름 
	cv::namedWindow(window_name);  // 이미지를 보여줄 윈도우 생성 
	cv::imshow(window_name, img); // 이미지 표현 

	cv::createTrackbar("Canny thresh:", window_name, &thresh, max_thresh, thresh_callback); // 이미지에 수평으로 컨트롤 할 수 있는 바 생성 ( 마지막 파라미터는 onChange 함수로 파라미터를 (int, void*) 형태로 가지고 있어야함 
	thresh_callback(0,0); // trackbar에 따라 외곽선 검출 결과를 보여주는 함수 
	cv::waitKey(0);

	return 0;
}

void thresh_callback(int, void*)
{
	cv::Mat canny_output;
	cv::Canny(gray_img, canny_output, thresh, thresh * 2); // Canny 알고리즘을 이용해서 물체의 엣지(edge)를 검출
	vector<vector<cv::Point> > contours; // Point = {int, int} = vector int가 2개 있는 
	vector<cv::Vec4i> hierarchy; // Vec4i = {int, int, int, int} = vector int가 4개있는 

	cv::findContours(canny_output, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); // binary 이미지에서 외곽선을 찾는다 

	cv::Mat drawing = cv::Mat::zeros(canny_output.size(), CV_8UC3);

	for (size_t i = 0; i < contours.size(); i++)
	{
		cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
		cv::drawContours(drawing, contours, (int)i, color, 2, cv::LINE_8, hierarchy, 0);
	}
	imshow("Contours", drawing);
}
  • opencv docs에서 새로운 함수를 알게되었다 
     createTrackbar - 설정한 윈도우에 설정한 변수의 값을 변경할 수 있는 바가 생성된다 
  • Edge 추출시 blur를 활용하여 불필요한 edge가 덜 나오게 할 수 있다. 
  • cv::findContours - 외곽선을 검출한다 
    • (input image, output image, contours, hierarchy, int mode, int method, cv::Point offset=cv::Points())
    • mode 의 종류 
      cv2::RETR_EXTERNAL : 외곽선중 가장 바깥쪽 선만 리턴한다 
      cv2::RETR_LIST : 모든 외곽선을 찾지만 계층관계는 구성하지 않는다 
      cv2::RETR_CCOMP : 모든 외곽선을 찾으며 2단계의 계층관계를 구성한다 
      cv2::RETR_TREE : 모든 외곽선을 찾으며 모든 계층관계를 구성한다 
      여기서 계층이란 검출된 외곽선 안에 있는 또다른 외곽선을 뜻한다 
    • method 의 종류
      cv2::CHAIN_APPROX_NONE : 모든 point를 저장
      cv2::CHAIN_APPROX_SIMPLE : 4개의 point만을 저장하여 메모리를 절약 
      cv2::CHAIN_APPROX_TC89_L1 : Teh_Chin 연결 근사 알고리즘 L1버전을 적용하여 point 개수를 줄임 
      cv2::CHAIN_APPROX_TC89_KCOS : Teh_Chin 연결 근사 알고리즘 KCOS 버전을 적용하여 point 개수를 줄임 
      return 값으로 오는 contours의 shape를 찍어보면 각각 method마다 shape이 다른것을 볼 수 있다.
  • cv::Scalar 를 이용하여 외곽선을 그릴 때마다 다른 색상을 적용 
  • cv::drawContours
  • (InputOutputArray image,
    InputArrayOfArrays contours,
    int contourIdx,
    const cv:Scalar &color,
    int thickness=1, int lineType=8,
    cv::InputArray hierarchy=noArray(),
    int maxLevel = 2147483657,
    cv::Point offset = cv::Point())

python.을 활용하여 개발을 진행하던 중 Edge device를 사용하면서 문제가 발생했다 

속도가 너무 느리고 메모리가 부족하다는 점이였다. 

그래서 C++을 활용하여 개발을 진행하고자 이전에 python으로 진행했던 opencv 예제를 C++로 변환하여 다시 진행하려한다. 

2021.06.23 - [Machine Learning/Computer Vision] - (1) OpenCV python - 기초 ( imread, imshow, imwrite, cvtColor, resize, waitkey, destroyAllWindows)

 

(1) OpenCV python - 기초 ( imread, imshow, imwrite, cvtColor, resize, waitkey, destroyAllWindows)

CNN 모델에 학습을 시키기 전 하고자 하는 목표에 맞게 이미지를 전처리하여 학습을 시키면 더 좋은 효과를 볼 수 있다. 이미지에서 필요없는 부분이 있는지, 특정 영역만을 추출해도 되는지 개

house-of-e.tistory.com

 

 

1. cv::imread 

string img_path = "asd";
Mat img = imread(img_path, IMREAD_COLOR);
  • 지정된 경로에 이미지를 읽어오는 함수이다 
  • 파라미터 - (const cv::String &filename, int flags =1 ) 
  • 첫번째 파라미터는 파일 이름으로 String형태의 문자열을 입력받는다 
  • 두번째 파라미터는 load할 color의 종류로 cv:: 에 여러가지 종류의 color type이 enum으로 정의되어있다
  • enum  ImreadModes {
    IMREAD_UNCHANGED=-1, 
    IMREAD_GRAYSCALE = 0, 
    IMREAD_COLOR = 1 // 기본값 
    ....
    }

2. cv::imshow

imshow("window_name", img);
  • 설정한 Window name을 가지는 창을 띄운다 
  • 파라미터 - (const cv::String &winname, cv::InputArray mat) 
  • 첫번째 파라미터는 생성할 윈도우의 이름으로 String형태로 문자열을 입력받는다 
  • 두번째 파라미터는 윈도우에 표시할 이미지 데이터로cv::InputArray형태로 입력받는다 

3. cv::waitKey

waitKey(0)
  • 사용자의 키 입력을 설정한 시간만큼 대기한다. 만약 0이면 무한대로 대기한다 
  • 파라미터 - (int delay=0) 
  • 첫번째 파라미터는 대기할 시간을 의미한다 단위는 milliseconds 이다 

4. cv::cvtColor : header -> (opencv2/opencv.hpp)

Mat grayImage;
cvtColor(img, grayImage, COLOR_BGR2GRAY);
Mat HSVImage; 
cvtColor(img, grayImage, COLOR_BGR2HSV);

  • 파라미터 - (cv::InputArray src, cv::OutputArray dst, int code, int dstCn = 0 ) 
  • 첫번째 파라미터는 변경을 하려는 이미지 
  • 두번째 파라미터는 변경한 결과를 저장할 변수 
  • 세번째 파라미터는  변경을 할 색상이다. enum으로 cv::ColorConversionCodes에 작성되어있다 
  • 네번째 파라미터는 결과물의 차원 수이다. 기본값은 0으로 0일시 원본이미지와 코드에서 자동적으로 차원이결정된다  

5. cv::resize 

Mat resizedImg; 
resize(img, resizedImg, Size(640, 640));
  • 파라미터 - (cv::InputArray src, cv::OutputArray dst, cv::Size dsize, double fx =(0.0), double fy = (0.0), int interpolation = 1 ) 
  • 세번째 파라미터는 변경하고자 하는 크기이다. cv::Size형태로 전달해야한다 
  • 네번째 파라미터는 수평(x축)에 적용되는 배율의 값이다 
  • 다섯번째 파라미터는 수직(y축)에 적용되는 배율의 값이다
  • 여섯번째 파라미터는 사용할 보간법의 종류를 설정할 수 있다. enum으로 InterpolationFlags에 정의되어있다

 

(2022-07-20) 현재에도 많이 사용하고 있는 방법인 CSP에 대한 논문이다 

CSP는 계속돼서 사용되어 지금은 Modified CSPv6까지 나왔다 

개인적인 생각으로는 기존 residual shortcut을 몇 개의(2~3개의) convolution마다 진행했다면 CSP shortcut은 그런 residual shortcut 몇 개마다 진행한다. 

 

Abstract 

  • CSP : Cross Stage Partial Network 
  • 기존의 CNN들의 computation이 많이 소모되는 이유는 네트워크 최적화 내의 중복 기울기 정보(Duplicated gradient information) 때문이다 
  • CSPNet은 시작과 끝에서 feature map을 통합하여 기울기의 variability를 respect 한다
  • 결과적으로, ImageNet dataset에서 동등한 정확도로 20% computations를 감소시켰다 
  • CSPNet은 쉽게 적용할 수 있고 기존 shortcut 방식들을 대체할 수 있는 일반적인 구조이다. 
    - transition layer : BN + 1x1 conv + 2x2 avg pooling 
    - growth rate : Dense Block을 지날 때마다 증가되는 feature map의 사이즈 

Dense connection

 

1. Introduction 

  • Neural Network가 더 깊고 넓어질 수 록 더 강력함을 보여줬다
  • 그러나 이런 확장 구조는 매우 많은 computations를 필요로 한다 
  • 이전의 몇몇 접근법에서는 모바일 GPU를 기준으로 설계되었다 ( depth-wise separable convolution) 
    depth-wise separable conv는 ASIC(Application-Specific Integrated Circuit)과 호환되지 않는다. 
  • 이 제안의 초점은 기본 layer의 featuer map을 두 부분으로 나누어 제안하는 cross-stage hierarchy에 따라 mergeing 한다 
  • 중요 개념은 gradient의 흐름을 다른 두 개의 네트워크 경로에 나누는 것이다 
  • CSPNet에서 주로 다루는 문제는 3가지이다 
    1. CNN의 학습능력(learning ability) 강화 
        - 경량화하면서도 충분한 정확도를 유지 
        - ResNet, ResNeXt, DenseNet등에 쉽게 적용 가능하고 computation effort를 10~20%가량 줄이지만 정확도는 거의 떨어지지 않거나 outperform 
    2. Removing computational bottlenecks 
        - 너무 많은 computational bottleneck은 inference process에서 더 많은 cycles이 필요하게 된다 
        - utilization을 더 효율적으로 높이고 필요 없는 에너지 소모를 줄인다 ( 저자 중에 College of Artificial Intelligence and Green Energy National Chiao Tung Univ가 있는데 친환경에너지 쪽이어서 그런지 이런 이야기가 좀 있다)
        - YOLOv3 기반 모델에서 computational bottleneck을 80%가량 감소시킨다 
    3. Reducing memory costs 
        - DRAN은 비싸고 크기도 큰데 DRAM사용량을 줄이면 ASIC의 비용을 감소시킬 수 있을 것이다 
        - 메모리 사용량을 줄이기 위해 cross-channel pooling을 이용 - feature pyramid 생성 과정에서 feature map의 compress를 수행한다 
        - PeleeNet에서 feature pyramids를 생성할 때 memory 사용량을 75% 감소할 수 있었다 
  • CSPNet은 CNN에서 학습 용량을 증가시킬 수 있고 작은 모델에서 더 좋은 정확도를 얻을 수 있다. 
    GTX 1080ti에서 109 FPS, 50%의 AP50을 얻었다 
    - 메모리 대역폭을 줄일 수 있었고 Nvidia jetson TX2에서 42% AP50, 49 FPS를 얻었다 

 

2. Related work 

  • CNN architectures design 
    ResNeXt 저자가 width, depth의 channel보다 cardinality(gradient의 다양성)를 늘리는 게 더 효과적일 수 있다 했다
    a. DenseNet 
        - 출력의 결과물을 그대로 concatenates 하여 다음 레이어의 인풋으로 사용했다 이러한 방식은 cardinality를 극대화할 수 있다
    b. SparseNet
        - dense connection은 exponentially spaced connection을 하여 parameter의 utilization을 효과적으로 개선할 수 있다 또한 높은 cardinality와 sparse connection이 gradient combination의 concept으로 왜 네트워크의 학습능력을 향상할 수 있는지 이유를 설명하고 partial ResNet(PRN)을 개발했다 

 

3. Method 

  • Cross Stage Partial Network 
    • DenseNet 
      아래 수식을 보면 DenseNet의 경우 역전파에서  $$ g_0, g_1,... g_{k-1} $$ 가 중복되어 사용됨을 볼 수 있다 
      이런 중복은 다른 레이어에서 같은 값을 중복해서 사용함을 의미한다 

 DenseNet에서의 순전파와 역전파 

 

  • 저자가 제안하는 CSPDenseNet은 입력 채널을 두 개의 파트로 분할한다 
    $$ x_0 = [x_0`, x_0``] $$ 두 개로 나누어진 파트는 스테이지의 마지막에서 직접적으로 연결된다 그리고 $$ x_0``$$ 는 dense block을 통과한다 
  • Dense layers의 출력 $$ [x_0``, x_1, ..., x_k]$$는 바로 transition layer를 통과하고 transition layer의 output $$x_t$$는 $$x_0``$$와 결합되고 바로 다른 transition layer를 통과하여 $$x_U$$를 생성한다. 

  • 위 공식을 보면 dense layer에서 들어오는 gradient 가 별도로 합쳐져 있음을 볼 수 있다 (gt) 
    또한 feature map x0`이 직접적으로 dense layers를 통과하지 않고 분리되어 통합된다 
    가중치 업데이트를 보면 두 갈래의 기울기 정보가 서로 중복되지 않는다. 
  • CSPDenseNet은 DenseNet의 feature reuse장점은 유지하면서 동시에 gradient flow를 truncation 하여 중복되는 gradient info의 과도한 양을 방지한다 
  • Partial Dense Blcok 
    - Incerease gradient path : feature map복사를 사용하여 gradient paths가 doubled 되는 것을 완화 
    - balance computations of each layer : 일반적으로 base layer의 채널의 개수는 growth rate보다 크다(input C 64, growth rate 32, 64,...) CSP는 base layer의 반절만 사용하기 때문에 계산 병목 현상의 절반 가까이를 효과적으로 해결할 수 있다 
    - reduce memory traffic : 총 m개의 dense layer가 있을 때
    $$ w\, \times \,h\, \times \, c,\, grouth\, rate\, :\, d, total \,m\, dense\, layers$$
    dense layer의 CIO(Convolution Input, Output)는
    $$ (c \times m ) + ((m^2 + m) \times d)/2 $$
    이고 partial dense block의 CIO는
    $$((c \times m)+(m^2 +m) \times d)/2$$
    일반적으로 m과 d는 C보다 매우 작기 때문에 partial block이 반절 가까이 네트워크 메모리 traffic을 절약할 수 있다 

 

 

  • 저자는 3가지 다른 방식의 Feature fusion 방식을 소개한다 
    1.  CSPDenseNet에 사용한 방식 두 갈래로 나누어진 경로에 하나는 Dense Block과 Transition layer를 지나고 그 뒤 두 갈래를 concatenate 한 후 다시 transition layer를 통과한다
    2. Fusion First는 두 갈래 중 하는 Dense Block을 통과하고 그 뒤 두 갈래를 합친 후 transition layer를 통과한다
    3. Fusion Last는 CSPDenseNet와 다르게 마지막에 두 갈래를 합치기만 하고 transition layer을 다시 지나가지 않는다
  • 위의 Fusion First와 Fusion Last는 다른 영향을 보여주는데 
    Fusion First는 두 갈래로 나뉜 기울기를 먼저 합친 다음 transition layer로 들어가기 때문에 많은 양의 기울기가 재사용된다 
    Fusion Last는 한 방향만 transition layer를 지나가고 그 뒤 두 갈래를 합치기 때문에 기울기의 흐름이 잘리기 때문에 기울기 정보가 재사용되지 않는다 
  • Fusion first는 실험 결과 Imagenet 데이터셋에서 computations는 효과적으로 감소되었고 정확도는 0.1% 밖에 감소되지 않았다
    Fusion Last 또한 computations는 감소되었지만 정확도가 1.5% 감소되었다 

일반 CNN에서의 CIO 계산 (출처 : HarDNet: A Low Memory Traffic Network )

  • 가정 1 
    - c 입력 채널 : 32 
    - m 몇개의 Dense layer : 3 
    - g growth rate : 32 
    $$ Dense \, layer :  (32 \times 3) + ((3^2 + 3) \times 32)/2 = (96 + (12 \times 32)/2 = 96 + 384 / 2 = 288 $$
    $$ partial \, dense \, block :  ((32 \times 3) + ((3^2 + 3) \times 32))/2 = ((96 + (12 \times 32))/2 = (96 + 384) / 2 =  240 $$
  • 가정 2 
    - c  : 4
    - m : 3 
    - g : 62
    $$ Dense \, layer :  (4 \times 3) + ((3^2 + 3) \times 64)/2 = (12 + (12 \times 64)/2 = 12 + 768 / 2 = 396 $$
    $$ partial \, dense \, block :  ((4 \times 3) + ((3^2 + 3) \times 64))/2 = ((12 + (12 \times 64))/2 = (12 + 768) / 2 =  390 $$
    - 채널과 growth rate의 차이가 매우 커야 Dense layer의 CIO가 더 커진다

ResNeXt에서 bottleneck 을 제거하고 transition layer을 사용하여 병목을 줄였다

 

4. Experiments 

  • Computational Bottleneck 
    기존의 ResXBlock에서 bottleneck layers를 제거하여 채널의 크기 변경을 감소시킴으로 인해 22%가량 computations를 감소시켰다 

PeleeNet에서 병목구간을 80% 가량 감소시켰다
ResNeXt에서 bottleneck layer를 제거함으로써 중간중간 아웃풋 채널의 개수가 일정하게 유지된다 ( computations 22% 감소 )

논문 : https://arxiv.org/pdf/1911.11929.pdf

 

최신 SOTA 모델인 YOLOX 논문이다 

기존 누구나 생각했던 YOLO의 문제점? 불편함이었던 사항들을 전부 수정한듯한 느낌이다 

기존 불편한점 : Anchor based model , Anchor 최적, ancho는 그저 mAP를 끌어올리기 위한 수단, 과연 coupled head는 regression loss와 classification loss, obj loss 중 어떤 게 더 많이 잘못됐는지 알고 있을까? end-to-end 학습을 위해 NMS를 없애고 싶다 

Abstract 

  • 기존의 YOLO 시리즈를 anchor-free로 변경하고 다른 디텍션 테크닉을 사용 
  • Decoupled head 
  • Leading label assignment strategy - SimOTA 
  • COCO 데이터셋에서 기존보다 3%정도 뛰어난 47.3%의 AP를 얻음 

Introduction 

  • 지난 2년간 객체인식 분야의 주된 관점은 anchor-free detectors와 end-to-end(NMS-free) detectors이다. 이런 기술들은 아직 YOLO시리즈에 접목되지 않았고 SOTA였던 YOLOv4와 YOLOv5 또한 anchor-based detector이어서 학습 시 hand-craft로 하이퍼 파라미터들을 수정해야 했다 
  • 저자는 기존의  YOLOv4와 5는 anchor-based에 과하게 최적화 되어있어 YOLOv3을 베이스 모델로 선택하였다 

YOLOX 

  • YOLOX-DarkNet53
    • YOLOv3 + DarkNet53을 베이스 라인으로 사용 
    • 300 epochs 학습 진행, 5 epochs는 COCO train2017로 warm-up 진행 
    • SGD momentum 0.9 사용 
    • Learning rate는 lr X BatchSize/64 ( linear scaling ), 초기 lr = 0.01
    • cosine scheduler 사용 
    • weight decay 0.0005 
    • batch size 128 ( 8-GPU) 
    • Input size = 448 ~ 832 ( 32 strides)
    • 모든 실험 FPS, latency는 모두 FP-16, batchsize 1 , Tesla V100으로 진행 
  • YOLOv3 baseline 
    • DarkNet53, SPP layer ( YOLOv3-SPP) 
    • 추가로 EMA weights updating 사용 ( EMA : Exponential Moving Average)
    • Cosine lr schedule 
    • IoU loss, IoU-aware branch
    • 학습때regression 브런치에 대해 IoU loss를 사용한 것이 큰 성능 향상을 불러왔다 한다 
    • Augmentations 
      • RamdomHorizontalFlip
      • ColorJitter
      • Multi-scale for data augmentation
      • Random Resized Crop은 Mosaic과 상충한다 생각되어 사용하지 않음 
  • Decoupled head 
    • 기존 객체인식에서 분류와 회귀의 잘 알려진 conflict가 있었다. 대부분의 decoupled head는 one-stage, two-stage에서 많이 사용되었지만 YOLO시리즈는 백본 및 feature pyramids가 지속적으로 변함에 따라 detection head는 coupled로 유지되었다 
    • 저자는 두가지 분석 실험을 통해 coupled detection head는 성능에 문제가 있단 걸 알았다 
    • YOLO의 head를 decoupled 로 변경하니 높은 성능 향상이 있었다 
      또한 decoupled head는 end-to-end 학습에 필수적이다 
    • Coupled head와 decoupled head를 비교한 결과 Coupled head에서는 4.2%의 성능 감소가 있었고 decoupled head에서는 0.8%의 성능 감소가 있어 YOLO detect head를 가벼운(lite) decoupled head로 대체하였다
      - 구체적으로 1x1 conv로 채널차원을 감소시키고 각각 두 개의 병렬 3x3 conv가 따른다

  • Strong data augmentation 
    • Mosaic과 MixUp 이용 – YOLOX의 성능을 boost함 
    • MixUp은 classification을 위해 design됐지만 객체 검출 학습에서 BoF(Bag of Freebies)로 수정되었다
    • MixUp과 Mosaic을 모델에 적용했고 마지막 15epochs에서는 close 하였다
    • Data augmentation을 사용한 후 ImageNet pre-training은 더 이상 효과를 보지 못했다 – 그래서 뒤의 모든 학습은 스크레치부터 학습하였다
  • Anchor-free
    • 기존의 YOLOv4, v5는 anchor-base 파이프라인으로 엥커 메커니즘은 많은 문제들이 있었다
      1. 검출 성능을 최적화하기 위해서는 엥커의 크기를 찾기 위해 클러스트링을 진행해야 한다. 이런 클러스트링은 학습 때마다 도메인을 특정(domain-specific)하게 되고 일반화에 좋지 않다
      2. anchor 메커니즘은 detection head의 복잡도와 이미지마다 예측하는 숫자를 증가시킨다. 이러한 메커니즘은 edge AI system에서 병목현상을 야기한다
    • Anchor-free detector는 디자인 파라미터의 수를 감소시키고 heuristic한 튜닝과 많은 트릭들(clustering, grid sensitive)이 필요하지 않게 된다
    • 기존의 YOLO를 anchor-free로 변경하는 것은 그렇게 어렵지 않다
      각 로케이션마다 3개씩 예측하던 것을 1개로 변경하고 직접 4개의 값을 예측하도록 하였다 (left-top corner, height, width)
    • location of each object as the positive sample and pre-define a scale range, as done in [29], to designate the FPN level for each object. – 이러한 수정이 파라미터와 GFLOPs를 감소시키고 성능을 증가시켰다
  • Multi positives 
    • 기존 YOLOv3의 할당 rule과 같게 anchor-free 버전 또한 하나의 positive sample을 할당하였다 ( 각 예측에 대해 다른 비슷하게 잘 예측한 예측은 무시)
      하지만 이러한 좋은 (할당하지 않는) 예측들은 가중치 update에 좋은 이득을 가져온다
      이러한 좋은 예측은 positive와 negetive와의 극심한 불균형을 완화시킨다
      저자는 간단하게 center에서 3x3 area를 positives로 정했다 – 이름은 center sampling이라고 했다
    • 결과적으로 45%로 성능이 향상됐고 ultralytics(YOLOv5 저자)의 yolov3의 성능을 이미 넘었다
  • SimOTA
    • Advanced label assignmetn는 객체 검출에 중요한 과정이다. 기존 저자의 연구인 OTA를 기준으로 4가지 insights를 정했다
      1. Loss/Quality aware 
      2. Center prior
      3. Dynamic number of positive anchors for each ground-truth
      4. Global view - 특히 OTA는 레이블 할당에 global 관점을 가지게 한다 
    • Optimal transport problem을 공식화할 수 있게 한다
    • 저자는 Sinkhorn-Knopp algorithm을 이용하여 OT problem을 해결한다.
      하지만 25%의 추가적인 학습 시간이 필요하다
      이것은 quite expensive 하기 때문에 OT(Optimal transport)를 간단히 한 SimOTA를 제안한다
    • Dynamic top-k starategy를 사용한다
    • SimOTA는 먼저 각 예측-GT 쌍에 대해 cost 또는 quality로 표시되는 pair 매칭 정도를 계산한다
    • Gt gi와 prediction pj와의 cost 계산은 다음과 같다 cij=LijclsLijreg 
      λ는 balancing coefficient
      LijclsLijreggt와 predictions의 classification loss와 regression loss를 뜻한다 
      그러고 나서 gt에 대해서 고정 중앙 영역 내에서 비용이 가장 적게 드는 Top-k 예측을 positive sample로 선택한다 
      마지막으로 이러한 positive prediction의 해당 grid는 positive로 할당된다
      k 값은 gt의 개수에 따라 다른 값을 가진다
    • SimOTA는 학습 시간만 감소시키는 것이 아닌 Simkhorn-Knopp algorithm의 solver 하이퍼파라미터들을 방지한다

  • End-to-End YOLO 
    • (Qiang Zhou, Chaohui Yu, Chunhua Shen, Zhibin Wang, and Hao Li. Object detection made simpler by eliminating heuristic nms. arXiv preprint arXiv:2101.11782, 2021)를 따라 두 개의 추가적인 conv, one-to-one label assignment, stop gradient를 추가한다
      1. 이러한 적용은 end-to-end 학습이 가능하게 하지만 약간의 성능 감소와 추론 속도 추가를 발생시킨다

  • Model size and data augmentation
    • 대부분의 모델에서 같은 룰을 적용했지만 augmentation 전략에서는 모델의 사이즈에 따라 몇 개의 차이가 있다 
    • 작은 모델의 경우(YOLOX-nano) 강한 data augmentation은 성능 감소가 따랐다 그렇기 때문에 MixUP의 경우 삭제하고 mosaic의 경우 scale range를 감소시켰다 [0.1,2.0] 🡪 [0.5,1.5]
    • 하지만 큰 모델의 경우 강한 augmentation이 성능 증가가 따랐다
      MixUP의 경우 기존의 버전보다 더 강하게 적용하였고
      Copypaste에서 영감을 받아 두 이미지를 혼합하기 전에 무작위로 샘플링된 scale factor로 두 이미지를 jittered 하였다
      MixUP에서 Scale jittering의 효과를 비교하기 위해 COPYpaste(YOLOX-L)와 비교하였다 ( Copypaste는 instance mask(segmentation)가 필요하지만 MixUP은 필요하지 않다)

 

이번 장에서는 openCV에서 어떤 타겟을 마치 정면에서 본 듯 만들 수 있는 투영 변환에 대한 글을 작성하겠다

이전 장에서도 이용했던 mouse_events를 이용한다 

import cv2 

class MouseEvents:
    def __init__(self) -> None:
        self.points = []

    def left_button_down(self, x, y, frame):
        self.points.append([x,y])  
        cv2.circle(frame, (x,y),5,(0,0,255),-1)
        cv2.imshow('origin',frame)
        print(x,y)      

    def right_button_down(self,x,y, frame):
        self.points.pop()
        print(x,y)      

    def onMouse(self, event, x, y, flags, img):
        if event == cv2.EVENT_LBUTTONDOWN:
            self.left_button_down(x,y, img)
        elif event == cv2.EVENT_RBUTTONDOWN:
            self.right_button_down(x,y,img)

1. 원근(투영) 변환 - perspective (projection) transform 

  • 원근  
     일반적으로 사람들이 2차원 화면에서 어떤것이 더 멀리 있는지 인식하는 방법은 원근법을 이용한 것이다 가까운 것은 크게, 멀리 있는 것은 작게, 자신의 초점이 있는 곳을 기준으로 한 점으로 모인다 
  • 투영이란 
    도형이나 입체를 다른 평면에 옮기는 일
  • 우리가 진행할 문제는 이미지에서 원하는 부분의 원근감을 없에는 문제이다. 

 

2. opencv perspective transform 

import cv2 
import numpy as np 

from mouse_events import MouseEvents

POINTS_CHECK = False
# 좌표점은 좌상->좌하-> 우상 -> 우하 
# 변경할 좌표를 4x2 행렬로 전환 
# 이동할 좌표를 설정
mouse_events = MouseEvents()
mouse_events2 = MouseEvents() 

path = 'images.jpg'
frame = cv2.imread(path)
height, width  = frame.shape[:2]
cv2.imshow('origin', frame)
if POINTS_CHECK != True:
    copy_frame = frame.copy()
    cv2.setMouseCallback('origin',mouse_events.onMouse, copy_frame)
    cv2.waitKey(0)
    # 원본 이미지에서 원하는 지점의 좌표
    source_coord = np.float32( mouse_events.points )
    # H와 W 는 새로 생성될 이미지(destination_coord)의 크기를 선택한 지점의 상하좌우 최대값으로 넣기 위해 구한다
    H = max(mouse_events.points[2][0],mouse_events.points[3][0]) - min(mouse_events.points[0][0],mouse_events.points[1][0])
    W = max(mouse_events.points[0][1], mouse_events.points[1][1], mouse_events.points[2][1], mouse_events.points[3][1] ) - min(mouse_events.points[0][1], mouse_events.points[1][1], mouse_events.points[2][1], mouse_events.points[3][1] )
    # 변환 이미지에서 원하는 지점의 좌표 ( (0,0) ~ (W,H)로 변환 )
    destination_coord = np.float32( [ [0,0], [0,H], [W,0], [W,H] ] )
    # 원본 -> 변환 미지수 8개를 구한다
    M = cv2.getPerspectiveTransform(source_coord, destination_coord)
    # 변환 -> 원본 미지수 8개를 구한다
    M2 = cv2.getPerspectiveTransform(destination_coord, source_coord)
	# 원본 -> 변환의 역행렬을 구한다 
    INV_M = np.linalg.pinv(M)
    
    
transformed = cv2.warpPerspective(frame, M, (W,H))
cv2.imshow('transformed', transformed)
  • opecv에서는 간단하게 원근변환을 할 수 있게 해주는 getPerspectiveTransform 함수와 warpPerspective 함수를 제공한다 
    getPerspectiveTransform은 이용을 하자면 미지수를 구하는 단순한 수학 계산이다. 사용하기 위해서는 해당 점이 이동할 위치를 알고 있어야 한다 또한 사용상 원하는 지점의 좌표를 선택할 때 좌상, 좌하, 우상, 우하 순서대로 입력해야 한다.

  • cv2.warpPerspective(frame, M, (W, H)) 
    첫 번째 파라미터로는 사용될 이미지의 데이터가 들어간다 
    두 번째 파라미터 M 은 getPerspectiveTranform을 통해 구한 변환 행렬이 들어간다 ( 아래 행렬식에서 P들이 M이 된다)
    세 번째 파라미터는 결과로 나오는 이미지의 높이, 넓이이다 

다음과 같이 M은 3x3의 행렬이다
미지수는 P11 ~ P32 8개

  • MouseCallback을 이용하여 원하는 영역의 꼭짓점 4개를 선택한 후 아무 키나 눌러 진행한다 

 

3. 역변환

  • 만약 변환된 이미지에서 특정 좌표가 원본 이미지에서 어느 지점인지 알고 싶다면 어떻게 할 수 있을까? 

  • 위 사진과 같이 변환된 이미지에서 파란 선의 좌표가 원본 이미지에서 어느 지점인지 알기 위해서는 이전에 구한 변환에 사용한 행렬의 역행렬을 구하여 변환 이미지에서 원하는 지점의 좌표와 행렬 곱해주면 된다 
if POINTS_CHECK != True:
    cv2.setMouseCallback('transformed',mouse_events2.onMouse, transformed)
    cv2.waitKey(0)
    line_coord = np.float32(mouse_events2.points)
    One = np.squeeze(INV_M @ np.expand_dims(np.append(line_coord[0], 1), axis=1))
    Two = np.squeeze(INV_M @ np.expand_dims(np.append(line_coord[1], 1), axis=1))
    One = One[:2] / One[2]
    Two = Two[:2] / Two[2]

    POINTS_CHECK = True

cv2.line(transformed, line_coord[0].astype(int), line_coord[1].astype(int), (255,0,0),2,1)
cv2.line(frame, One.astype(int), Two.astype(int), (255,0,0),2,1)

dst2 = cv2.warpPerspective(transformed, M2, (height, width))
cv2.imshow('transform2', dst2)

cv2.imshow('origin', frame)
cv2.imshow('transformed', transformed)
cv2.waitKey(0)
  • 행렬곱은 간단하게 @를 이용하여 구할 수 있다. 
  • 행렬의 길이가 서로 다르니 맞추기 위해 np.append로 값 1을 넣었다 그 후 하나의 차원을 추가해 3x1의 차원을 가지도록 만들었다 ( 3x3 @ 3x1 ) 
  • 위의 결과로 나온 One 은 wx, wy, w 형태이다 ( 위의 공식 참고 ) 그렇기 때문에 원본의 x, y를 구하기 위해서는 w를 구하여 나누어줘야 한다. 
  • M = P11 ~ P 32 
  • 원하는 지점 = x, y를 x, y, 1로 변경 
  • One = (wx`, wy`, w) 

위의 공식을 잘 이해하고 있다면 무리 없이 진행할 수 있다. 복잡한 수학 계산은 컴퓨터가 해주니 공식을 가지고 사용하면 된다! 

 

이미지 시퀀스( 비디오) 입력을 받아 특징을 추출하는 방법 중 3D Conv를 이용한 방법에 대한 논문이다 

비디오를 사용하여 분석할 수 있는 분야는 행동인식, 추천, 검색, 순위, 이상행동 감지, 활동 이해 등이 있다 

 

Abstract 

  • 저자는 대용량 지도학습 데이터셋에 대하여 3D Conv를 이용하여 쉽고 효과적으로 시공간 특징을 학습함을 제안한다
  • 저자의 연구는 세가지 갈래를 찾았다 
    • 1.  3D conv와 2D Conv의  시공간 특징 학습을 비교 
    • 2. 3x3x3 conv kernel을 사용한 architectures가 가장 좋은 성능을 보임 
    • 3. 간단한 선형분류기를 이용하여 4개의 다른 밴치마크에서 SOTA를 달성 다른 2개의 밴치마크에서도 견줄만한 성과 달성 UCF101 데이터셋에 대하여 52.8% 정확도를 얻고 빠른 inference로 인해 계산에 매우 효율적이다. 
  • 최종적으로 C3D는 매우 간단하고 학습과 사용이 쉽다 

1. Introduction 

  • 인터넷이 빠르게 성장함에 따라 매분 수많은 비디오가 공유되고 있다. 정보의 폭발에서 비디오 분석은 다양하게 활용될 수 있다. 그렇기 때문에 비슷한 대용량 비디오 작업에 일반적인 비디오 설명기(descriptor)가 필요로 하게 되었다 
  • 저자는 효율적인 비디오 설명기에 4가지 속성이 있어야한다고 본다 
    • 1. Generic : 다양한 타입의 비디오를 잘 설명해야한다 
    • 2. Compact : millions 비디오를 작업할 수 있도록 딱 맞아야 한다
    • 3. efficient : 몇 천 개의 비디오는 real world시스템에 매분 적용될 수 있어야 한다 
    • 4. simple : 간단한 모델이 여야 한다 (ex : linear classifier - 저자는 linear SVM 사용)
  • 기존의 연구들의 대부분은 이미지를 이용한 분석이었다 해당 방법의 문제점은 시간적(temporal) 정보를 잃어버린다는 점이었다 그래서 저자는 시공간 특징을 학습하는 3D ConvNet을 제안한다
  • 이전에도 3D ConvNets을 이용한 연구가 있었지만 저자의 제안은 다양한 대용량 지도 학습 데이터셋 분석에서 좋은 성능을 보였다

2. Related Work 

  • Laptev and Lindeberg - spatio-temporal interest points ( STIPs) : Harris corner detector를 3D로 확장
  • SIFT and HOG : 3D로 확장함 SIFT-3D, HOG3D 
  • Dollar et al. - Cuboids features for behavior recognition
    (P. Dollar, V. Rabaud, G. Cottrell, and S. Belongie. Behavior recognition via sparse spatio-temporal features. In Proc. ICCV VS-PETS, 2005)
  • Sadanand and Corso - ActionBank for action recognition
    (S. Sadanand and J. Corso. Action bank: A high-level representation of activity in video. In CVPR, 2012.)
  • Wang et al. - Dense Trajectories (iDT) 'SOTA' 
    시간 정보와 공간정보를 따로 처리하여 SOTA 달성 Harris corner를 3D로 확장하고 Densely-sampled 된 특징 포인트들을 optical flow를 이용하여 추적한 후 수작업으로 만들어진 궤적을 추출
    성능은 좋지만 대규모 데이터 셋에서 다루기 어려움 - 계산량이 많음 
  • 강력한 병렬 처리기계로 인해 ConvNets는 좋은 성능을 보여왔고 human pose estimation에 대하여 이미지와 비디오에서도 좋은 성능을 보여왔다 
    A. Jain, J. Tompson, M. Andriluka, G. W. Taylor, and C. Bregler. Learning human pose estimation features with convolutional networks. In ICLR, 2014
  • ConvNets을 이용한 연구들은 좋은 성능을 보였지만 역시 다양하고 큰 데이터셋에서는 계산이 많이 필요하게 된다 
  • 3D ConvNet은 full 비디오를 입력으로 받기 때문에 대용량 데이터셋에도 scaling 이 쉽다 
  • 기존에도 3D Conv를 이용한 실험이 있었지만 저자는 3D Conv와 3D pooling을 이용하여 시공간 정보가 처음부터 끝까지 모든 레이어에 전파될 수 있도록 하였다 

3. Learning Features with 3D Convnets

2D Conv의 ouput과 3D Conv의 output에 대하여 3D Conv는 시공간 정보가 유지된다는 것을 보여주기 위한 그림 

  • 저자는 실험을 통해 어떤 kernel을 사용함이 최적의 성능을 보이는지를 실험한다 
  • Conv kernel의 표현 $$ c \times l \times h \times w  $$ 
    • c : 채널의 개수 
    • l : 프레임의 길이 
    • h , w : 프레임의 높이, 넓이 
  • pooling kernel의 표현 $$d \times k \times k $$ 
    • d : 시간적 깊이 ( frame 개수로 이해, 첫 번째 압축을 제외한 다음부터는 프레임 개수로 보기에는 무리가 있음)
  •  Common network settings
    • 101개의 다른 행동에 대하여 분류 
    • 모든 비디오는 128 X 171로 resize ( UCF101 데이터셋에 대하여 반절 정도임)
    • 비디오를 겹치지 않는 16개의 프레임으로 나누어서 네트워크에 인풋으로 사용 
    • $$ input : 3 \times 16 \times 128 \times 171 $$
    • jittering과 random crop(3x16x112x112) 사용 
    • 총 5개의 conv layers와 5개의 pooling와 2개의 FCL와 마지막에 softmax 사용 
    • 각 5개의 conv layers의 필터 개수는 64, 128, 256, 256, 256 사용 
    • 적절한 padding 사용 stride 1 사용 
    • 모든 pooing layer는 2x2x2 max pooling stride 1 사용 - 아웃풋은 인풋에 비해 8배 압축 
    • 가장 첫 번째 pooling layer는 시간적 정보를 너무 빠르게 합치지 않기 위해 1x2x2 사용 ( 2D pooling)
    • 두 개의 FCL은 2048의 아웃풋을 가지며
      학습은 scratch부터 학습
      mini-batches 30 사용
      init lr : 0.003  4 epochs마다 10씩 나눔 
      16번의 epochs가 지나고 학습 멈춤 
  • Varying network architectures
    • 저자가 메인으로 제안한 점은 어떤 커널이 가장 성능이 좋았냐다 그래서 Conv kernel에서 d에 해당하는 depth를 1~ 7로 변경하며 성능을 측정했다 ( depth 1은 2D와 같음)
    • $$ c \times d_i \times h \times w    $$
  • 실험 1
    • 모두 같은  kernel size  d : (1,1,1,1,1 ) ( 3,3,3,3,3) (5,5,5,5,5) (7,7,7,7,7)
  • 실험 2 
    • 증가와 감소 
    • (3-3-5-5-7) 증가 
    • (7-5-5-3-3) 감소
  • 각 실험에서 파라미터 개수의 차이는 마지막에 있는 FCL의 파라미터 개수 비중이 크기 때문에 너무 작아 별 상관하지 않았다는..... 

 

depth 3 이 가장 좋았다

 

  • 결론적으로 위 실험을 통해 depth는 3으로 고정하고 pooling도 첫 번째를 제외하고 2x2x2 사용하고 마지막 FCL들의 아웃풋을 4096으로 고정 
  • Dataset 
    • Sports-1M dataset에 학습 ( 가장 큰 비디오 분류 데이터셋 487개의 스포츠 종류 )
    • UCF101과 비교 - Sports-1M이 5배 많은 카테고리를 가지고 100배 정도 더 많은 데이터셋
  • Training
    • 렘덤으로 비디오에서 5개의 2초 길이 클립 추출 각 클립은 128x171로 resize 
    • 학습 시에는 공간, 시간적 jittering을 위해 16 x 112 x 112로 랜덤 크롭 , 50% 로 수평 반전
    • SGD mini-batch 30 사용 
      init lr 0.003 , 150K iter마다 2로 나눔 
      1.9M iter에서 학습 종료 ( 대략 13 epochs)
    • scratch 학습과 I380K에 대하여 pre-trained model을 fine-tuned 하여 사용 

최종 실험 아키텍쳐
기존 모델들과 성능 비교

  • What does C3D learn? 
    • 과연 3D conv가 어떤 것을 보고 판단하는지를 판별하기 위하여 deconvolution method를 이용하여 C3D를 설명함 
      (Visualizing and understanding convolutional networks. In ECCV, 2014)
    • C3D가 처음 몇 프레임에서는 모양에 초점을 맞추고 후속 프레임에서는 두드러지는 움직임을 추적한다는 것을 관찰함

  • 잘 보이지 않지만 첫 번째 체조 비디오의 경우 처음에는 사람에 대해 가중치가 활성화되어있고 후속 프레임으로 갈 수 록 사람의 행동에 가중치가 활성화됨을 볼 수 있음 
  • 두 번째 사진의 경우 눈에 활성화가 되어있고 후속 프레임에는 눈의 움직임에 가중치 활성화가 되어있음 

4. Action Recognition

  • C3D is compact
    • C3D의 compactness를 평가하기 위해 UCF101 데이터셋에 대하여 PCA를 사용하여 더 낮은 차원으로 projection 하여 linear SVM에 사용하여  정확도를 봄
    • 아래 그래프를 보면 더 낮은 차원에서 C3D의 정확도가 월등히 높은 것을 볼 수 있다 
    • fc6의 features를 추출하여 비디오에 대해서 좋은 일반적인 특징들을 학습하는지 보기 위해 t-SNE를 이용하여 시각화함

 

차원에 따른 정확도 그래프
t-SNE를 이용한 시각화

 

5. Action Similarity Labeling 

  • Dataset
    • ASLAN dataset 사용 - 3,631개의 비디오, 432개의 액션 클래스 
    • 두 개의 비디오를 보고 같은 행동인지 다른 행동인지 판별 
    • 데이터셋 10-fold 사용 
    • 테스트셋에는 한번도 본적없는 비디오를 이용하기 때문에 매우 도전적인 과제임 
  • Features 
    • 비디오를 8개의 프레임이 겹쳐지는 16프레임의 비디오 클립으로 자름 
    • C3D에서 prob 와 fc7, fc6, pool5 에서 특징을 추출함 - 각 feature를 평균한 뒤 L2 norm을 이용하여 계산
  • Classification model 
    • (A. Jain, J. Tompson, M. Andriluka, G. W. Taylor, and C. Bregler. Learning human pose estimation features with convolutional networks. In ICLR, 2014) 와 똑같은 설정 사용 
    • 4가지 타입 특징에 대하여 12개의 다른 distances를 제공( 48-차원 12 x 4 ) 
    • 각 distances는 서로 비교될 수 없기 때문에 각각이 0의 평균과 unit variance를 갖도록 독립적으로 정규화 

Action similarity 결과

 

6. Scene and Object Recognition 

  • Datasets
    • 다양한 장면 인식을 위해 두개의 벤치마크 데이터셋 사용 - YUPENN, Maryland
    • object recognition에는 egocentric dataset사용  일상용품 42종
    • YUPENN
      • 420개의 비디오 14개의 장면 카테고리
    • Maryland 
      • 130개의 비디오 13개의 장면 카테고리
    • Classification model 
      • 모든 데이터셋에 대하여 같은 특징 추출 설정 사용하고 선형 SVM으로 분류 
      • leave-one-out evaluation 사용 (leave-one-out evaluation protocol as described by the authors of these datasets.)
      • 비디오를 16개의 프레임으로 자르고 각 클립에서 가장 잘 발생하는 레이블을 정답 레이블로 설정
        클립에서 가장 자주 발생하는 레이블이 8 프레임 미만이라면 객체가 없는 negative 클립으로 간주하고 훈련과 테스팅 모두에서 버렸다

Imagenet과의 비교

7. Runtime Analysis

Method와 CPU GPU에 따른 속도 비교

Appendix 

  • 인풋 해상도의 효과 

  • C3D가 학습한 특징 시각화
    • conv2 a 
       - low-level motion pattenrns 학습 주로 움직임의 edges, blobs, short changes, edge orientation changes, color changes 
    • conv3b
       - 좀 더 큰 모서리 움직임 패턴, textures, body parts, trajectories 
    • conv5b
       - 움직이는 원형 물체, 자전거 타기와 같은, 움직임과 같은 더 복잡한 움직임 패턴을 학습함 

conv2a
conv3b
conv5b
conv5b
conv5b
optical flow와의 비교 - optical flow는 모든 픽셀에 집중하지만 C3D는 눈에 띄는 동작에만 주의를 기울임

 

기존 깊은 Convolution network의 vanishing gradient 문제는 Residual short cut을 통해 극복하였고 이제는 어떻게 하면 더 적은 파라미터로 좋은 성능을 낼 수 있는가가 되었다 

이후로는 어떻게 이전 레이어의 정보를 다음 레이어에 효율적으로 전달할 수 있는가? 가 주된 관심사가 되었다 

 

1. Abstract 

  • Cnn은 객체 인식 머신러닝에서 뛰어난 성과를 보였고 CNN에서 중요한 점은 네트워크의 깊이이다 LeNet부터 시작하여 Highway Networks, Residual Network들은 depth가 100이 넘게 레이어를 쌓았다 
  • CNN에서 네트워크가 점점 깊어지면서 생긴 문제점은 vanishing gradient와 washout이다. ResNet과 Highway Network는 Identity connection을 통하여 하나의 레이어에서 다음 레이어로 signal을 연결한다 
  • Stochastic depth shortens ResNet은 더 좋은 gradient flow를 위하여 학습중에 레이어를 무작위로 삭제하여 ResNet을 단축한다 
  • FractalNets은 네트워크에서 하나의 인풋에 대하여 여러 short paths로 나누어  Convolution을 적용하여 Residual을 학습하는 것과 비슷하게 네트워크를 깊게 쌓을 수 있
  • 위에서 설명한 네트워크들은 다양한 네트워크 구조와 트레이닝 절차에 대하여 다양한 접근법을 제시하지만 모두 같은 요점이 있다 - short path로 이전 레이어와 이후 레이어를 연결한다는 점이다 
  • 저자의 제안은 위에서 제안한 방식들에서 좋은 점만 빼서 네트워크안 레이어 사이의 최대의 정보 흐름을 보장한다 
    • 직접적으로 서로 다른 레이어를 연결한다 (feature map size를 amtching 한다 )
    • Feed-forward 특성을 유지하기 위해 각 레이어는 모든 이전 레이어에서 추가 입력을 얻고 모든 후속 레이어가 가진 feature-map을 전달한다 
    • ResNet과는 다르게 DenseNet은 Short cut을 summation하지 않고 concatenation 한다 
  • 이 연결패턴의 직관적이지 않는 부분은 기존 convolution net보다 더 적은 파라미터가 필요하다는 것이다 - 중복된 feature map을 다시 학습할 필요가 없기 때문에 
  • 각 계층은 이전 계층에서 상태를 읽고 후속 계층에 write 한다
    상태를 변경도 하지만 보존해야 하는 정보도 전달한다 
  • DenseNet은 네트워크에 추가되는 정보와 이전 레이어의 정보를 명시적으로 구분한다 
  • DenseNet은 매우 narrow 하다 ( 레이어당 12개의 필터 )
  • 각각의 레이어는 loss function과 원본 입력 signal의 gradients에 직접적으로 접근할 수 있다 
    또한 dense connection이 regularizing 효과도 있는 것을 관찰했다 
    overfitting을 감소시키고 학습 셋의 사이즈를 작게 가져갈 수 있다 

2. DenseNet

  • 기존의 ResNet의 공식은 \( x_L\) = \(H_L(x_L - 1) + x_L-_1) \)  이였다 
  • ResNet의 장점은 identity function을 통해 다음의 레이어에 이전 레이어의 가중치가 직접적으로 흐를 수 있다는 것이었다 하지만 여기서 identity function은 아웃풋 \(H_L\)은 summation울 포함하게 되는데 이는 정보의 흐름을 방해할 수 있다 
  • Dense Connectivity
  • 1) 모든 이후 레이어에 이전의 모든 레이어를 직접적으로 연결한다 

  • 2) \(x_L = H_L([x_0, x_1, ... , X_L-_1]\) 
  • 위의 수식은 x들끼리의 feature map의 size가 같지 않으면 실행될 수 없다 하지만 convolution network에서 필수적인 부분은 downsampling(pooling 레이어)이다. 
  • pooling을 가능하게 하기 위해 densely 연결된 dense block을 나눈다 ( 위 사진에서 Dense Block 1, 2, 3 ) 
  • 여기서 Dense Block 사이에 들어가는 Conv, Pooling을 transition layer라고 부르겠다 한다 
  • Batch norm과 1x1 conv, 2x2 avg pooling을 진행한다 

 

      • Growth rate 
      • 저자는 DenseNet에서 매우 narrow 한 레이어 구조를 갖기 위해 Growth rate라는 하이퍼파라미터를 추가하였다 
      • 해당 파라미터는 하나의 Dense Block을 지날 때마다 증가되는 feature map의 사이즈를 말한다 
      • 만약 \(H_L \) 이 k개의 feature map을 만든다면 다음 레이어는 \(k_0 + k x (L-1)\)의 input feature map을 가진다 

 

  • Bottleneck layers 
  • 각 레이어는 k개의 output feature-map을 가지지만 인풋은 더 크게 가질 수 있다 
  • 3x3 conv 이전에 1x1 conv를 이용하여 인풋 feature-maps의 개수를 줄일 수 있다 이는 또한 computational efficiency를 얻을 수 있다 - 이는 특히 narrow 한 DenseNet에 이점이 많다 
  • BN - ReLu - Conv(1x1) - BN - ReLu - Conv(3x3) 
  • 저자의 실험에 기반하여 각 1x1 conv가 4k의 feature-map을 생성하도록 한다 

 

  • Compression
  • 모델의 compactness(소형화)를 더 향상하기 위해 transition layer에서 feature-map의 개수를 감소시킨다 
  • Transition layer는 \(\theta m\)개의 output feature-map을 갖는다 0 < \(\theta\) < 1 
    DenseNet-C는 \(\theta\)를 0.5로 하였다 

 

  • Implementation Details 
  • 3x3 Conv는 입력에 대해 동일한 크기의 feature-maps를 유지하기 위해 zero padding 추가 
  • DenseBlock 사이에 transition layer추가 ( 1x1 conv, 2x2 avg pooling)
  • 마지막 레이어에는 global avg pooling 사용 
  • 각 3개의 Dense Block은 32x32, 16x16, 8x8의 feature map이 반복 
  • 기본 DenseNet 설정은 {L=40, k=12}, {L=100, k=12}, {L=100, k=24}
  • DenseNet-BC(B=bottleneck, C=Compression)는 {L=100, k=12}, {L=250, k=24}, {L=190, k=40}
  • ImageNet에서는 입력 이미지 224x224에 대하여 맨 처음 conv를 7x7 kernel에 stride 2 적용 

3. Experiments

  • 아래 그림에서 ResNet은 augmentation을 한것과 안한것의 정답률 차이가 컸는데 DenseNet에서는 작았다 이는 DenseNet이 보다 더 overfitting에 강하다는 것을 보여준다 
  • 파라미터 개수도 이전보다 작고 정답률이 향상됨을 볼 수 있다 

+는 augmentation한 것

  • Training 
  • 모든 데이터셋에 SGD 사용 
  • 초기 learning rate는 0.1로 하고 epochs가 50%, 75% 진행됐을 때 10씩 나눔 
  • ImageNet은 총 90 epochs에서 30, 60 때 10배씩 감소 
  • Weight decay 0.0001 이용, Nesterov momentum 0.9 사용 dampening 없이 
  • 다음에서 소개된 가중치 초기화 방식도 사용  ( K. He, X. Zhang, S. Ren, and J. Sun. Delving deep into rectifiers: Surpassing human-level performance on imagenet classification. In ICCV, 2015.)
  • Dropout 0.2 사용 ( 맨 처음 conv에서는 안 함)

4. Discussion 

  • 기존 ResNet과의 차이점은 단지 결합과 더함의 차이이다 하지만 이 작은 수정이 두 아키텍처의 동작을 크게 다르게 했다 
  • Input 결합의 직접적인 결과로 DenseNet은 이후의 모든 레이어의 feature map에 직접적으로 접근할 수 있었다 
  • 이는 네트워크 전체에서 feature reuse를 장려하고 더 딱 맞는(compact) 모델로 만들 수 있게 한다 
  • 모든 히든 레이어마다 classifier를 추가하는 Deeply-supervised Net과 비슷하고 차이는 하나의 loss function이 모든 레이어에 공유되므로 덜 복잡하다는 점이다 
  • 학습 중간에 무작위적으로 레이어를 삭제하는 stochastic depth regularization을 DenseNet에 이용하면 insight를 제공할 수 있을 거라 말한다 

  • 여기서 재밌는 그래프는 위의 feature reuse 그래프이다 
  • 실제로 모든 레이어가 이전 레이어의 feature-map에 접근하는지 알아보기 위해 레이어 s 와의 연결에 할당된 가중치 평균을 계산하였다 
  • 두 번째 세 번째 Dense block을 보면 맨 처음 source layer에서 뿌려지는 가중치가 일관되게 최소 가중치를 할당하는 것을 볼 수 있다 이는 transition layer에 의해 압축이 되었다는 것을 알 수 있다. 
  • 앞서 언급한 \(\theta\)에 의해 압축이 잘 됐다는 것을 알 수 있다 
  • 마지막 classification 레이어를 보면 Dense block 내부의 가중치를 전부 사용하지만 마지막 쪽 feature map에 집중하는 거로 보아 네트워크 후반에 생성된 더 높은 수준의 feature에 집중함을 알 수 있다

 

 

+ Recent posts