[Computer Vision] SVD(Singular Value Decomposition)에 대하여
이 글에서는 SVD가 무엇인지 간단하게 설명하고, Computer Vision와 AI에서 언제 어떻게 쓰이는지 예시와 함께 다룰 예정입니다. (본인 공부 및 기록용)😁
SVD란?
SVD는 행렬을 분해하는 방법 중 하나입니다. ‘특이값 분해’라고도 불리는데, 복잡한 행렬을 더 간단한 형태로 나누어줍니다. 이를 통해 데이터의 패턴을 발견하거나 차원을 축소하는 등 다양한 응용이 가능합니다.
SVD는 다음과 같은 세 개의 행렬로 원래의 행렬을 분해합니다.
$ A = U \sum V^T $
A: m * n 행렬, U: m * n 직교 행렬, 열 벡터들은 A의 좌특이 벡터, $\sum$: m * n 대각 행렬, 대각선 성분들이 A의 특이값(Singular Values), $V^T$: n * n 직교 행렬, 행 벡터들은 A의 우특이 벡터,
컴퓨터 비전에서의 SVD 활용
컴퓨터 비전에서 SVD는 주로 이미지 압축, 노이즈 제거, 객체 인식 등 여러 작업에 사용됩니다.
이미지 압축
이미지 압축은 이미지를 저장하거나 전송할 때 필요한 공간을 줄이는 데 중요합니다. SVD를 사용하면 고차원의 이미지를 저차원으로 압축한 후, 필요한 경우 다시 복원할 수 있습니다. 아래 코드를 실행하면, 원본 이미지와 압축된 이미지를 비교할 수 있습니다. k 값을 조절하면서 압축률과 이미지 품질의 변화를 볼 수 있습니다.
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, color
from skimage.transform import resize
# 예제 이미지 불러오기
image = color.rgb2gray(data.astronaut())
image = resize(image, (256, 256))
# SVD 분해
U, Sigma, VT = np.linalg.svd(image)
# k 값을 통해 차원 축소
k = 50
compressed_image = np.dot(U[:, :k], np.dot(np.diag(Sigma[:k]), VT[:k, :]))
# 원본 이미지와 압축 이미지를 시각화
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.title('Original Image')
plt.imshow(image, cmap='gray')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.title('Compressed Image with k = {}'.format(k))
plt.imshow(compressed_image, cmap='gray')
plt.axis('off')
plt.show()
노이즈 제거
이미지에는 종종 노이즈가 포함되어 있을 수 있습니다. SVD를 사용하여 노이즈를 제거하고 더 깨끗한 이미지를 얻을 수 있습니다. 이는 특히 의료 이미지나 위성 이미지 처리에서 중요합니다.
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, color, util
# 예제 이미지 불러오기
image = color.rgb2gray(data.astronaut())
image = resize(image, (256, 256))
# 노이즈 추가
noisy_image = util.random_noise(image, mode='gaussian', var=0.1)
# SVD 분해
U, Sigma, VT = np.linalg.svd(noisy_image)
# k 값을 통해 차원 축소 및 노이즈 제거
k = 50
denoised_image = np.dot(U[:, :k], np.dot(np.diag(Sigma[:k]), VT[:k, :]))
# 원본 이미지, 노이즈 이미지, 노이즈 제거 이미지를 시각화
plt.figure(figsize=(15, 5))
plt.subplot(1, 3, 1)
plt.title('Original Image')
plt.imshow(image, cmap='gray')
plt.axis('off')
plt.subplot(1, 3, 2)
plt.title('Noisy Image')
plt.imshow(noisy_image, cmap='gray')
plt.axis('off')
plt.subplot(1, 3, 3)
plt.title('Denoised Image with k = {}'.format(k))
plt.imshow(denoised_image, cmap='gray')
plt.axis('off')
plt.show()
포인트 클라우드 분산과 기하학적 구조 분석
포인트 클라우드 데이터에 SVD를 적용하면, 데이터의 주요 방향을 찾고 데이터의 분산을 분석할 수 있습니다. 예를 들어, 포인트 클라우드의 분산이 가장 큰 방향을 찾는 데 SVD를 사용할 수 있습니다. 이를 통해 포인트 클라우드의 주요 축을 결정할 수 있습니다.
C++ 코드에서 SVD의 적용 포인트 클라우드 데이터에서 SVD를 사용하여 키포인트 프레임을 선택하는 과정은 다음과 같습니다.
1. 데이터 샘플링: 포인트 클라우드에서 일정 비율로 샘플링된 데이터를 선택합니다.
2. 중심 계산: 샘플링된 데이터의 중심을 계산합니다.
3. SVD 수행: 샘플링된 데이터에 대해 SVD를 수행합니다.
4. 특이값 비교: SVD의 결과로 얻어진 첫 번째 특이값을 임계값과 비교하여 키포인트 프레임을 선택합니다.
5. 변환 적용 및 저장: 선택된 포인트 클라우드에 변환을 적용하고 저장합니다.
void processPointCloudSVD(
const std::vector<std::string>& ply_files,
const std::vector<Matrix4f>& transforms,
PointCloud::Ptr& merged_pointcloud,
std::vector<Vector3f>& centers,
std::vector<int>& used_indices,
std::vector<std::pair<PointCloud::Ptr, std::string>>& save_queue,
std::mutex& mtx,
float sampling_ratio,
float svd_threshold,
int start_idx,
int end_idx,
const std::string& sampling_folder) {
/*
SVD를 사용하여 포인트 클라우드를 처리하는 함수입니다. 샘플링, SVD 수행, 첫 번째 특이값과 임계값 비교, 변환 및 저장의 과정을 수행합니다.
*/
for (int i = start_idx; i < end_idx; ++i) {
PointCloud::Ptr cloud(new PointCloud);
if (!customPlyRead(ply_files[i], cloud)) {
continue;
}
// 샘플링
PointCloud::Ptr sampled_points = randomSample(cloud, sampling_ratio);
Vector3f current_center = computeCenter(sampled_points);
// 데이터 준비
Eigen::MatrixXf data(sampled_points->size(), 3);
for (size_t j = 0; j < sampled_points->size(); ++j) {
data(j, 0) = sampled_points->points[j].x;
data(j, 1) = sampled_points->points[j].y;
data(j, 2) = sampled_points->points[j].z;
}
// SVD 수행
Eigen::MatrixXf U, V;
Eigen::VectorXf S;
performSVD(data, U, S, V);
// 첫 번째 특이값과 임계값 비교
bool should_add = false;
if (S(0) > svd_threshold) {
should_add = true;
std::lock_guard<std::mutex> lock(mtx);
centers.push_back(current_center);
used_indices.push_back(i);
}
// 선택된 포인트 클라우드 변환 및 저장
if (should_add) {
Matrix4f transform = transforms[i];
PointCloud::Ptr transformed_points(new PointCloud);
pcl::transformPointCloud(*sampled_points, *transformed_points, transform);
{
std::lock_guard<std::mutex> lock(mtx);
*merged_pointcloud += *transformed_points;
std::ostringstream oss;
oss << sampling_folder << "/" << std::setw(4) << std::setfill('0') << used_indices.size() - 1 << ".ply";
std::string save_file_path = oss.str();
save_queue.emplace_back(transformed_points, save_file_path);
}
}
}
}
void performSVD(const Eigen::MatrixXf& data, Eigen::MatrixXf& U, Eigen::VectorXf& S, Eigen::MatrixXf& V) {
/*
주어진 데이터 행렬에 대해 SVD를 수행하는 함수입니다. Eigen 라이브러리를 사용하여 SVD를 수행합니다.
*/
Eigen::JacobiSVD<Eigen::MatrixXf> svd(data, Eigen::ComputeThinU | Eigen::ComputeThinV);
U = svd.matrixU();
S = svd.singularValues();
V = svd.matrixV();
}
차원축소
고차원 데이터를 낮은 차원으로 줄이면서도 중요한 정보를 유지하는 기법입니다. SVD는 이 과정에서 주로 사용되며, 데이터의 패턴을 파악하고 계산 효율성을 높이는 데 도움을 줍니다.
import numpy as np
from sklearn.decomposition import TruncatedSVD
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
# 데이터 로드
digits = load_digits()
X = digits.data
y = digits.target
# SVD를 사용한 차원 축소 #
svd = TruncatedSVD(n_components=2)
X_reduced = svd.fit_transform(X)
# 결과 시각화
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y, cmap='viridis')
plt.colorbar()
plt.title('Digits dataset reduced to 2D using SVD')
plt.show()
LSI(Latent Semantic Indexing)와 SVD
LSI는 텍스트 데이터를 분석해 문서 간의 유사성을 파악하는 데 사용됩니다. SVD는 텍스트 데이터의 단어-문서 행렬을 분해하여 숨겨진 의미적 구조를 파악하는 데 중요한 역할을 합니다.
from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import TfidfVectorizer
# 예제 문서 데이터
documents = [
"The cat in the hat disabled his powers.",
"A quick brown fox jumps over the lazy dog.",
"The sun is shining in the blue sky."
]
# TF-IDF 행렬 생성
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(documents)
# SVD를 통한 LSI 수행
svd = TruncatedSVD(n_components=2)
X_lsi = svd.fit_transform(X)
print("LSI로 변환된 문서 벡터:")
print(X_lsi)
추천 시스템 (Recommendation Systems)와 SVD
추천 시스템에서는 사용자-아이템 행렬을 SVD로 분해하여 사용자와 아이템 간의 잠재력 관계를 파악합니다. 이를 통해 사용자가 좋아할 만한 아이템을 예측할 수 있습니다.
import numpy as np
from scipy.sparse.linalg import svds
# 예제 사용자-아이템 평점 행렬
ratings = np.array([
[5, 4, 0, 0],
[4, 0, 0, 2],
[0, 0, 5, 4],
[0, 3, 4, 0],
])
# SVD 수행
U, sigma, Vt = svds(ratings, k=2)
sigma = np.diag(sigma)
# 예측 행렬 계산
predicted_ratings = np.dot(np.dot(U, sigma), Vt)
print("예측된 평점 행렬:")
print(predicted_ratings)
이것저것 공부하면서 SVD에 대해 새로 알게 되는 내용은 계속 추가할 예정입니다. 궁금한 것들이나 추가 및 수정했으면 좋겠는 거 말해주시면 좋을 거 같아요. 좋은 하루 보내시길 바래요 :)
댓글남기기