※ 주의사항 ※
본 블로그는 수업 내용을 바탕으로 제가 이해한 부분을 정리한 블로그입니다.
본 내용을 참고로만 보시고, 틀린 부분이 있다면 지적 부탁드립니다!
감사합니다😁
안녕하세요!!
오늘은 아래와 같은 내용을 확인해보겠습니다.
Color Image Processing
칼라 이미지 영상처리
지난번엔 [미니프로젝트 Ver 1] Gray Scale Image Processing을 주제로 프로젝트를 작업했다면 이번에는 [미니프로젝트 Ver 2] Color Image Processing을 주제로 작업을 하였습니다. 이미지의 영상처리 효과는 대부분 지난 프로젝트와 동일합니다. 오히려 어떤 부분은 삭제된 부분도 존재합니다 (1차원 미분방식의 수평&수직 엣지 추출, 확대(포워딩), 회전(포워딩). 또 반대로 추가된 기능들도 있습니다. (채도 변경, 색상 추출)
추가된 기능들은 본 글에서 코드에 대한 설명을 하겠지만 그 외의 기존 기능들에 대한 알고리즘은 지난 프로젝트의 글을 참고 바랍니다. 이번 글은 지난 프로젝트와 비교해서 [미니프로젝트 Ver 2.0]에서 추가나 변경된 주요 부분을 짚고 넘어가겠습니다!!
# 본 프로젝트 코딩소스 #
1. 칼라 이미지 영상처리 시연 동영상
1-1. 시연 동영상 - ① : 주요 추가 기능 + 화소점 처리
1-2. 시연 동영상 - ② : 기하학 처리 +히스토그램 처리
1-3. 시연 동영상 - ③ : 화소영역 처리 + 경계선 검출
2. 칼라 이미지 영상처리 개요
2-1. 디지털 칼라 이미지 영상처리 과정
(1) Color Image 사용
GrayScale Image : BinaryReader를 통해서 화소점의 값을 출력했다면,
Color Image : Bitmap 클래스를 사용해서 GetPixel 메서드를 통해 화소점의 RGB값을 출력한다.
(사용할 수 있는 확장자는 jpg, png, bmp 등 다수)
(2) Open Image
GrayScale Image : Open할 이미지의 폭(inW)과 높이(inH)를 계산하기위해 Math.Sqrt 메서드를 사용했다. (raw 확장자는 가로와 세로의 크기가 동일하기 때문에)
Color Image : Bitmap.Width와 Bitmap.Height 속성을 사용한다.
GrayScale Image : 파일의 내용을 메모리에 할당하기위해 BinaryReader.ReadByte() 메서드를 사용했다면
Color Image : Bitmap.GetPixel 메서드를 사용해서 ColorImage 파일의 RGB값을 저장한다.
(3) Display Image
GrayScale Image : Color.FromArgb메서드를 통해 동일한 값을 각 화소점에 입력한다.
→ FromArgb(ink, ink, ink)
Color Image : Color.FromArgb메서드를 통해 각 화소점의 RGB 값을 저장한다.
→ FromArgb(inkR, inkG, inkb)
Bitmap.SetPixel 메서드를 활용해서 PictureBox에 영상처리 된 이미지를 출력한다.(GrayScale과 동일)
2-2. 디지털 칼라 이미지 영상처리 공통 코드
(1) 전역 변수부
// 전역 변수부
byte[,,] inImage = null, outImage = null; // 3차원 배열이기 때문에 쉼표 3개
int inH, inW, outH, outW;
string fileName;
Bitmap paper, bitmap; // bitmap 변수는 열고 저장할때의 임시 통로
const int RGB = 3, RR = 0, GG = 1, BB = 2;
사용자 정의 함수부에서 자주 사용될 변수들은 전역변수로 선언
입력 이미지와 출력 이미지의 화소 값을 저장하는 배열은 3차원으로 생성 (R, G, B 각각의 배열 존재)
이미지를 열고 표시할 때 사용될 임시 통로용 변수 선언 (bitmap)
(2) openImage()
void openImage()
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.DefaultExt = "";
ofd.Filter = "Color Filter | *.jpg; *.png; *.bmp; *.tig;"; // 파일 열기 하면 기본적으로 보이는 파일 Type
if (ofd.ShowDialog() != DialogResult.OK)
return; // "OK" 버튼을 눌르면 넘어가고 누르지 않으면 돌아감
fileName = ofd.FileName;
// 파일 --> 비트맵
bitmap = new Bitmap(fileName); // 파일을 bitmap 변수에 임시 저장
// 인간이 생각하는 폭과 높이와 컴퓨터가 인식하는 폭과 높이는 다르기 때문에 바꿔서 저장
inH = bitmap.Width;
inW = bitmap.Height;
// 입력 메모리 할당
inImage = new byte[RGB, inH, inW]; // 면이 3개, 높이, 너비
// 비트맵 --> 메모리 할당
for (int i = 0; i < inH; i++)
for (int k = 0; k < inW; k++)
{
Color c = bitmap.GetPixel(i, k);
inImage[RR, i, k] = c.R;
inImage[GG, i, k] = c.G;
inImage[BB, i, k] = c.B;
}
equalImage();
}
Windows Form의 <OpenFileDialog> 객체를 호출해서 사용
Bitmap(파일경로) 메서드를 통해 이미지 파일을 bitmap 변수에 저장하고 Width와 Height 메서드를 사용해 이미지의 폭과 높이를 계산
Bitmap.GetPixel 메서드를 사용해서 각 화소의 R,G,B 값을 뽑아내고 inImage에 차곡차곡 저장 (3중 for문 사용)
(3) displayImage()
void displayImage()
{
paper = new Bitmap(outH, outW); // OUTPUT 종이
PB_OutImage.Size = new Size(outH, outW); // OUTPUT 액자
this.ClientSize = new Size(outH + toolStrip1.Width + 20, outW + menuStrip1.Height + 50);
// PictureBox를 화면의 완전 가운데. (this.Size)
int clientX = this.ClientSize.Width + toolStrip1.Width;
int clientY = this.ClientSize.Height;
// PictureBox의 왼쪽 위 꼭지점의 위치를 찾는과정
PB_OutImage.Left = (clientX - PB_OutImage.Width) / 2;
PB_OutImage.Top = (clientY - PB_OutImage.Height) / 2;
Color pen; // 펜 : 콕콕 찍을 용도
// 출력 영상 보기
for (int i = 0; i < outH; i++)
{
for (int k = 0; k < outW; k++)
{
byte inkR = outImage[RR, i, k];
byte inkG = outImage[GG, i, k];
byte inkB = outImage[BB, i, k];
// 펜에 잉크 묻히기, RGB 3색이 같아야 회색조
pen = Color.FromArgb(inkR, inkG, inkB);
paper.SetPixel(i, k, pen);
}
}
PB_OutImage.Image = paper; // 액자에 종이 걸기
}
Windows Form의 <PictureBox> 객체를 사용해서 이미지 출력
ClinetSize 속성을 통해 메인폼의 크기를 결정하고 메인 폼의 폭과 높이를 사용해서 PictureBox의 위치 지정
Color Image의 RGB 값을 FromArgb 메서드의 매개변수에 입력하고 SetPixel 메서드를 사용해서 이미지 표시(Display)
(4) saveImage()
void saveImage()
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.DefaultExt = "";
sfd.Filter = "PNG 파일 (*.png) | *.png";
if (sfd.ShowDialog() != DialogResult.OK) return;
String saveFname = sfd.FileName;
// Bitmap을 거쳐서 저장 (open도 동일하게 Bitmap 거쳐감)
// 칼라 이미지 파일의 압축 형식이 복잡하기 때문에
bitmap = new Bitmap(outH, outW); // 빈 비트맵 준비
// 비트맵에 픽셀을 찍을때는 3개의 rgb값을 한번에 찍기때문에 3중 for문이 아닌 2중 for문 사용
for (int i = 0; i < outH; i++)
{
for (int k = 0; k < outW; k++)
{
Color c;
c = Color.FromArgb(outImage[RR, i, k], outImage[GG, i, k], outImage[BB, i, k]);
bitmap.SetPixel(i, k, c); // 비트맵에 한점 콕 찍기!
}
}
// using System.Drawing.Imaging;
bitmap.Save(saveFname, ImageFormat.Png);
}
Window Form의 <SaveFileDialog> 객체를 호출해서 사용
openImage() 방식과 동일하게 Bitmap 클래스를 사용해서 파일 저장
→ Color Image 파일의 압축 형식이 복잡하기 때문에
→ GrayScale Image의 경우는 BinaryWriter 클래스를 사용해서 저장(Bitmap 미사용)
2-3. [미니프로젝트 Ver 2.0]의 주요 변경 부분 - ① : 마우스로 설정한 영역만 이미지 처리
mouseEvent를 활용해 PictureBox 위에서 마우스를 클릭 & 드래그를 통해 사용자가 원하는 영역을 설정 가능
※ Mouse Event 별 행동 ※
1. MouseDown
- 사각형 : 마우스 처음 좌표쌍 저장
- 다각형 : (좌클릭) 다각형의 꼭지점에 해당하는 좌표쌍 저장
(우클릭) 다각형을 종료 시키고 이미지 영상처리 수행
- 자유형 : 마우스 처음 좌표쌍 저장
2. MouseMove
- 사각형 : 마우스 *러버밴드(Rubber Band) 표시
- 다각형 : (해당사항 없음)
- 자유형 : 움직이는 동안 마우스 좌표쌍 계속 저장
3. MouseUp
- 사각형 : 마우스 마지막 좌표쌍 저장 + 마지막 러버밴드 지우기
- 다각형 : (해당사항 없음)
- 자유형 : *마우스 잔상 제거 + *마우스 속도 최적화 작업 수행 + 이미지 영상처리 수행
자유형의 경우 마우스가 지나가는 경로마다 마우스 좌표쌍을 저장하는데 해당 이미지 영상처리를 수행할때 마우스 좌표를 저장하는 배열의 값을 기준으로 각화소점이 기준점 밖인지 아닌지를 for을 통해 구분한다.
본 프로젝트에서는 자유형 마우스가 좌표쌍을 저장하기 위한 배열의 Size를 5000으로 주었다.
그렇기에 자유형의 마우스로 이미지 영역처리를 할 경우 속도가 현저히 늦어짐을 확인 할 수 있다.
따라서 마우스 속도 최적화 작업을 영역설정이 끝나고 나서 이미지 영상처리 전에 수행하도록 한다. (2.5 참고)
2-4. [미니프로젝트 Ver 2.0]의 주요 변경 부분 - ② : 러버밴드 + 마우스 잔상 효과
(1) 마우스 러버밴드
마우스 러버밴드란 흔히 우리가 마우스를 Click & Move시 생기는 일종의 가상선(직선)을 의미
본 프로젝트에서는 시작점과 종료점의 좌표값을 기준으로 가상의 사각형의 정보를 얻고 해당하는 값들의 R, G, B 값을 반전(255 - @)시켜 시각화 해준다.
마우스 러버밴드는 사용자의 편의성을 위해 만든 일종의 가상선으로 MouseUp시 발생하는 이벤트에서 러버밴드를 지워준다. (러버밴드를 지우지 않으면 이미지 표시에 방해가 된다)
※ 러버밴드와 관련된 소스코드는 별도로 첨부된 소스파일 참고 (코드가 다소 깁니다.. ㅠ😥)
(2) 마우스 잔상 효과
마우스 잔상효과 또한 러버밴드와 비슷한 효과로 마우스를 Click & Move 했을때 마우스가 지나온 길을 가상의 선(자유선)으로 표시하는 효과
본 프로젝트에서는 마우스가 지나가는 각 점마다의 R, G, B 값을 반전(255 - @)시켜서 시각화 해준다.
러버밴드와 동일하게 MouseUp시 잔상효과를 제거해 준다.
※ 마우스 잔상효과 코드
void drawPoint(int px, int py) {
Color c;
byte r, g, b;
try
{
c = bitmap.GetPixel(px, py);
r = (byte)(255 - c.R);
g = (byte)(255 - c.G);
b = (byte)(255 - c.B);
c = Color.FromArgb(r, g, b);
bitmap.SetPixel(px, py, c);
PB_OutImage.Image = bitmap;
}
catch { }
}
2-5. [미니프로젝트 Ver 2.0]의 주요 변경 부분 - ③ : 마우스 속도 최적화 작업
자유형 영역 이미지 처리 수행시 속도가 현저히 늦어지게 되는데 그 이유는 이미지를 영상처리해야 하는 부분을 찾는 과정에 있어서 너무 많은 연산을 수행하기 때문이다. 따라서 속도를 최적화 하는 작업은 연산하는 과정을 줄여주는 과정과 일치한다.
(1) 성능향상 1 : 좌표점의 포인트 개수 줄이기
const int SCALE = 5; // 5배 축소
int[] newMx = new int[mCount / SCALE];
int[] newMy = new int[mCount / SCALE];
자유 마우스를 사용하기 위해 마우스가 움직일때마다 좌표점의 값을 배열에 저장하는데 크기가 5000의 배열을 사용한다.
for (int i = 0; i < mCount / SCALE; i++)
{
try
{
newMx[i] = mx[i * SCALE];
newMy[i] = my[i * SCALE];
}
catch { }
}
mx = newMx;
my = newMy;
mCount = mCount / SCALE;
MouseUp 이벤트에서 적용되는 코드로 좌표쌍이 저장될 때마다 mCount 변수가 1씩 증가한다.
좌표쌍이 저장된 배열의 크기를 SCALE 비율 만큼 줄이고, 좌표쌍에 대한 값도 SCALE 비율만큼 줄여서 다시 저장한다.
새로 저장된 좌표쌍의 값은 pointInPolygon() 이란 함수의 매개변수로 사용되어 영상처리를 할 영역을 구분하는데 사용 된다.
(2) 성능향상 2: 포인트가 포함된 박스 찾기
pointInPolygon함수는 이미지 영상처리를 하기위한 영역을 찾기위해 (0,0)인 지점부터 for문을 통해 끝까지 찾는다.
0,0부터 찾는 방법은 너무 비효율 적인 방법이기에 속도를 개선하기 위해서 영역을 설정한 좌표쌍중에 최대 및 최소점을 찾고 해당 하는 점부터 영역을 찾아나가는 방법으로 속도를 개선하고자 한다.
minX = maxX = mx[0];
minY = maxY = my[0];
for (int i = 0; i < mCount; i++)
{
if (mx[i] < minX) minX = mx[i];
if (mx[i] > maxX) maxX = mx[i];
if (my[i] < minY) minY = my[i];
if (my[i] > maxY) maxY = my[i];
}
MouseUp 이벤트에서 적용되는 코드로 좌표쌍이 저장되는 배열의 최대값과 최소값을 구한다.
else if (mouseStatus == "Free")
{
if ((minX <= k && k <= maxX) && (minY <= i && i <= maxY))
{
if (pointInPolygon(k, i, mx, my))
processing();
else outImage[rgb, i, k] = inImage[rgb, i, k];
}
else outImage[rgb, i, k] = inImage[rgb, i, k];
}
이미지가 영상처리 되는 메인 알고리즘에서 자유형 마우스로 영역을 설정한 범위의 최대값(maxX, maxY)과 최소값(minX. minY) 사이에서만 알고리즘을 수행하도록 if 문을 구성한다.
2-6. [미니프로젝트 Ver 2.0]의 주요 변경 부분 - ④ : 파일 메뉴의 기능들 단축키 추가 (키보드 이벤트)
Main Form의 KeyDown 이벤트를 생성해서 각 메뉴마다 단축키 부여
본 프로젝트에서는 파일 메뉴(열기 / 닫기 / 종료)에 해당하는 기능들만 단축키를 부여하였으며, 이미지 처리 기능에 대한 단축키는 존재하지 않음
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Alt)
{
switch (e.KeyCode)
{
case Keys.F4: this.Close(); break;
}
}
if (e.Control)
{
switch (e.KeyCode)
{
case Keys.O: openImage(); break;
case Keys.S: saveImage(); break;
}
}
}
2-7. [미니프로젝트 Ver 2.0]의 주요 변경 부분 - ⑤ : HSL을 활용한 이미지 영상처리 효과 추가
RGB는 빨강(RED), 초록(GREEN), 파랑(BLUE)의 빛의 삼원색을 이용하여 표현하는 방식
HSL(HSV)는 사람이 색상을 인식하는 방식과 비슷한 방법으로 색상(HUE), 채도(Saturation), 명도(Lightness)를 써서 특정한 색을 표현하는 방식
→ 본 프로젝트에서는 RGB로는 처리할 수 없는(힘든) 채도 변경 이나 칼라 추출에 대한 영상 효과를 HSL 변환을 통해 수행한다.
RGB to HSL 변환은 C#의 Color 구조체에서 제공하지만 HSL to RGB 변환은 제공하지 않기 때문에 오픈 코드 활용
※ HSV 관련 Color 구조체 ※
1. Color.GetHue() : HSL의 색상 값 추출
2. Color.GetSaturation : HSL의 채도 값 추출
3. Color.GetBrightness : HSL(HSV)의 밝기 값 추출
✔ HSV 이미지 영상처리 효과는 아래의 "3.3 채도 변경" 및 "3.4 색상 추출" 내용 참고
2-8. 디지털 칼라 이미지 영상처리 알고리즘 공통 코드
이미지의 영상처리를 위한 메인 알고리즘(reverse, grayScale, gamma, etc...)에서 공통적으로 반복되는 코드에 대해 짚고 넘어가겠습니다.
// 마우스 영역 선택창이 "None"일때.
if (processType == '0') {
sx = 0; ex = inH;
sy = 0; ey = inW;
}
for (int rgb = 0; rgb < RGB; rgb++) {
for (int i = 0; i < inH; i++) {
for (int k = 0; k < inW; k++) {
// 마우스 영영 선택창이 "None" 이거나 "Box"일때
if (processType == '0' || mouseStatus == "Box")
{
if ((sx <= i && i <= ex) && (sy <= k && k <= ey))
outImage[rgb, i, k] = (byte)(255 - inImage[rgb, i, k]);
else outImage[rgb, i, k] = inImage[rgb, i, k];
}
// 마우스 영역 선택창이 "Poly"일때처리 수행
else if (mouseStatus == "Poly")
{
if (pointInPolygon(i, k, mx, my))
outImage[rgb, i, k] = (byte)(255 - inImage[rgb, i, k]);
else outImage[rgb, i, k] = inImage[rgb, i, k];
}
// 마우스 영영 선택창이 "Free"일때
else if (mouseStatus == "Free")
{
if ((minX <= k && k <= maxX) && (minY <= i && i <= maxY))
{
if (pointInPolygon(k, i, mx, my)) // Poly와 k,i 순서가 반대.
outImage[rgb, i, k] = (byte)(255 - inImage[rgb, i, k]);
else outImage[rgb, i, k] = inImage[rgb, i, k];
}
else outImage[rgb, i, k] = inImage[rgb, i, k];
}
}
}
}
MouseStatus의 Text 값에 따라 영상처리 알고리즘의 for문이 수행되는 조건이 달라진다.
(1) "None" || "Box"
x좌표 : sx ~ ex / y좌표 : sy ~ ey 사이에 해당하는 화소점만 영상처리를 수행한다.
→ "None"인 경우에는 sx와 sy의 값은0, ex와 ey는 각각 inH와 inW로 inImage 배열 전체에 해당된다.
(2) "Poly"
pointInPolygon 함수를 수행해서 마우스로 설정한 영역의 범위내에 해당하는 화소점만 영상처리를 수행한다.
(3) "Free"
성능향상 1과 2를 거쳐서 산출한 4개의 좌표점에 해당하는 범위 내에서만 pointInPolygon 함수를 수행하고 영상처리를 수행한다. → 성능향상 1,2에 대한 내용은 "2.5 [미니프로젝트 Ver 2.0]의 주요 변경 부분 - ③ : 마우스 속도 최적화 작업" 내용 참고
3. 화소점 처리
화소점 처리 : 다른 화소 값의 영향을 받지 않고 단순히 해당 화소 점의 값만을 변경 한다.
3-1. 반전 / 그레이 스케일 / 밝게 & 어둡게 / 선명하게 & 흐릿하게
3-2. 흑백모드(127 기준) / 감마 보정 / 파라볼라 (밝은 부분 강조 & 어두운 부분 강조)
3-3. 채도 변경
기존의 이미지 영상처리 효과는 색상을 표현하는 방식중에 RGB값을 이용해서 영상처리를 했다면 앞으로 보게될 영상처리 효과 두개는 RGB가 아닌 HSV(색상/채도/명도) 값을 이용해서 영상처리를 수행한다.
→ 다른 이미지 영상처리효과에 대한 알고리즘 설명은 지난 미니프로젝트 Ver 1.0 관련 글을 참고하거나 위의 소스코드 파일을 참고 바랍니다!!
채도값을 추출하고 변경하기 위해서는 RGB값을 HSV로 변환해야 하는데 RGB → HSV로 변환은 C#의 Color 클래스에서 손쉽게 수행이 가능하다.
하지만 다시 HSV → RGB로의 변환은 Color 클래스에서 제공하지 않기 때문에 별도의 함수를 사용해야하는데 이는 오픈 소스를 이용해서 처리하도록 하겠다.
(1) 변수 생성
Color c; // 한점
double hh, ss, vv; // 색상, 채도, 밝기
int rr, gg, bb; // 레드, 그린, 블루
RGB 값과 HSV값을 저장할 변수를 생성한다.
(2) RGB → HSV 전환
rr = inImage[RR, i, k];
gg = inImage[GG, i, k];
bb = inImage[BB, i, k];
//RGB --> HSV
c = Color.FromArgb(rr, gg, bb);
hh = c.GetHue();
ss = c.GetSaturation();
vv = c.GetBrightness();
RGB값을 미리 생성해둔 변수에 저장하고, Color의 GetSataturation() 메서드를 통해 채도 값을 추출한다. ( c = FromArgb(rr,gg,bb)는 구조체를 생성하는 코드로 사전에 수행되어야할 동작입니다.)
→ 사실 색상(Hue)과 명도(Brightness)는 사용하지 않기 때문에 변수 선언 및 변환 과정을 하지 않아도 됩니다..
(3) 채도 변경 및 HSV → RGB 전환
// 채도 변경
ss += 0.1;
//HSV --> RGB
HsvToRgb(hh, ss, vv, out rr, out gg, out bb);
채도 값을 변경해주고 HSV 값을 RGB로 컨버팅 해주는 함수를 사용해서 RGB값을 다시 추출한다.
이후에는 RGB값을 outImage[rgb,i,k]의 배열에 각각 저장 한다.
✔ HsvToRgb() 함수 코드는 위쪽에 별도로 첨부한 소스코드 파일을 참고 바랍니다!!!
3-4. 색상 추출
ColorDialog를 통해 사용자로부터 색상값을 전달받고(colorValue) 이를 각 화소점의 색상값(Hue)과 비교해서 일정한 범위내에 들어오는 색상이면 강조해주고 범위 밖의 색상이면 GrayScale로 변환해서 출력해준다.
rr = inImage[RR, i, k];
gg = inImage[GG, i, k];
bb = inImage[BB, i, k];
//RGB --> HSV
c = Color.FromArgb(rr, gg, bb);
hh = c.GetHue();
// 사용자가 지정한 색의 인근값 추출
if (colorValue - 15.0 < 0.0) colorValue = 15.0;
else if (colorValue + 15.0 > 360.0) colorValue = 345.0;
if (colorValue - 15 <= hh && hh <= colorValue + 15)
{
for (int rgb = 0; rgb < RGB; rgb++)
{
int px = inImage[rgb, i, k]+30;
if (px > 255) px = 255;
else if (px < 0) px = 0;
outImage[rgb, i, k] = (byte)px;
}
}
else
{
int gray = (inImage[RR, i, k] + inImage[GG, i, k] + inImage[BB, i, k]) / 3;
gray -= 50;
if (gray > 255) gray = 255;
else if (gray < 0) gray = 0;
outImage[RR, i, k] = (byte)gray;
outImage[GG, i, k] = (byte)gray;
outImage[BB, i, k] = (byte)gray;
}
4. 기하학 처리
영상을 구성하는 화소의 공간적 위치를 변경 및 재배치 한다.
4-1. 축소 / 확대 / 회전
4-2. 상하 반전 / 좌우 반전
5. 히스토그램 처리
이미지의 명암 분포를 확인하고 전체적으로 균일하게 변경 및 재배치 한다.
5-1. 흑백(평균) / 평활화
평활화 : 어둡게 촬영 된 이미지의 히스토그램을 조절하여 명암 분포가 빈약한 영상을 균일하게 만든다.
5-2. 스트레칭 / 엔드-인 탐색
스트레칭 : 명암 대비를 향상시키는 연산으로, 낮은 명암 대비를 보이는 영상의 화질을 향상시키는 방법
엔드-인 탐색 : 일정한 양의 화소를 인위적으로 지정해서 히스토그램의 분포를 더 균일하게 만듦
Color Image에서 히스토그램, 특히 스트레칭 효과를 프로그래밍 후 확인해 보니 효과가 GrayScale Image에 비해 미비하였다. 확인 결과 스트레칭 및 엔드-인 탐색은 RGB의 영역이 아닌 HSV 영역에서 V(Brightness)값을 변환해주어야 한다는 것을 알 수 있었다.(스트레칭 및 엔드-인 탐색은 명암과 관련된 효과이기 때문에..(?))
따라서 스트레칭과, 엔드-인 탐색은 지난 미니프로젝트 Ver 1.0과는다르게 HSV영역에서 화소값을 수정해보는 방향으로 알고리즘을 바꿔보았다.
(1) 명도의 최대 및 최소값 확인
double low = 1.0, high = 0.0;
for (int i = 0; i < inH; i++)
{
for (int k = 0; k < inW; k++)
{
rr = inImage[RR, i, k];
gg = inImage[GG, i, k];
bb = inImage[BB, i, k];
//RGB --> HSV
c = Color.FromArgb(rr, gg, bb);
vv = c.GetBrightness();
if (vv < low) low = vv;
if (vv > high) high = vv;
}
}
Microsoft의 Docs의 Color클래스 관련 설명을 확인해보니 c.GetBrightness에서는 0.0에서 1.0사이의 값을 반환해준다고 하여서 high의 초기값을 0, low의 최소값을 1.0으로 선언하였다.
그리고 명도값만을 추출해서 변환할 것이기 때문에 Color.GetBrightness 메서드를 통해 명도를 추출하고 최대 및 최소값을 찾아준다.
(2) RGB → HSV 변환
rr = inImage[RR, i, k];
gg = inImage[GG, i, k];
bb = inImage[BB, i, k];
//RGB --> HSV
c = Color.FromArgb(rr, gg, bb);
hh = c.GetHue();
ss = c.GetSaturation();
vv = c.GetBrightness();
(3) 스트레칭 연산 수행 및 HSV → RGB 변환
double outValue = ((vv - low) / (high - low) * 1.0);
if (outValue < 0.0) outValue = 0.0;
else if (outValue > 1.0) outValue = 1.0;
vv = outValue;
HsvToRgb(hh, ss, vv, out rr, out gg, out bb);
outImage[RR, i, k] = (byte)rr;
outImage[GG, i, k] = (byte)gg;
outImage[BB, i, k] = (byte)bb;
(4) 스트레칭 알고리즘 대비 엔드-인 탐색 알고리즘에서 추가된 Line
low += 0.2; high -= 0.2; // 스트래칭 알고리즘 대비 추가된 Line
엔드-인 탐색은 인위적으로 영역을 지정해서(잘라내서) 히스토그램의 분포를 더 균일하게 만드는 과정이기 때문에 최대와 최소값을 가감해준다.
6. 화소영역 처리
입력 화소값 뿐만 아니라 그 주위의 화소값도 함께 고려해서 출력 화소값을 결정한다.
6-1. 엠보싱 / 가우시안 필터(저주파 통과 필터) / 고주파 통과 필터
엠보싱 : 입력 영상을 양각 형태로 보이게 하는 효과
가우시안 필터 : 영상의 세세한 부분을 제거하여 부드러운 효과 제공 (대표적인 저역 통과 필터)
고주파 통과 필터 : 이미지 성분 중 고주파 성분은 통과하고 저주파 성분은 차단
6-2. 블러링 / 샤프닝(HIGH) / 샤프닝(LOW)
영상의 세밀한 부분을 제거하여 영상을 흐리게 하거나 부드럽게 하는 기술
사용자가 마스크의 크기를 입력하여서 블러링의 효과를 조절할 수 있다
블러링과는 반대되는 개념으로 디지털 영상에서 상세한 부분을 더욱 강조하여 표현
샤프닝의 MASK 중앙 값에 따라 샤프닝의 효과가 결정된다. (LOW : 5 / HIGH : 9)
7. 경계선 검출
화소영역 처리의 일부분에 해당되며, 디지털 영상의 경계선을 찾아내는 기술이다.
7-1. 유사 연산자 / 차 연산자
가장 간단한 에지 추출 기법으로 다른 방식과는 다르게 마스크를 통한 회선연산을 수행하지 않음
7-2. 로버츠 엣지 / 프리윗 엣지 / 소벨 엣지
1차 미분 회선 마스크를 이용한 에지 검출 방식 (로버츠 엣지 / 프리윗 엣지 / 소벨 엣지)
7-3. 라플라시안 연산자 / LOG 연산자 / DOG 연산자
2차 미분 회선 마스크를 이용한 에지 검출 방식 (라플라시안 연산자 / LOG 연산자 / DOG 연산자)
댓글