iT邦幫忙

3

[筆記]C++ & C#影像處理-色彩空間轉換

前言

最近幾個禮拜都在忙甄試資料,所以沒甚麼時間發文章,今日終於解脫所以發表一篇文章慶祝/images/emoticon/emoticon81.gif,廢話不多說趕緊切入正題了。
色彩空間轉換在影像處理當中佔了相當重要的位置,在我們使用亮度、色度等等有時候都會需要做色彩空間轉換計算,因使用其它色彩空間可方便處理或壓縮色彩加快達到目標。
這次介紹灰階、HSV與YCbCr,三種色彩空間,主要使用實作方式讓讀者能更加瞭解特性。

前置作業

延伸上一篇程式碼,將Write移到general,在色彩空間轉換直接使用一維陣列處理,除了不用轉為二維陣列之外,主要是因為她都是在處理一個像素,未來會講到Block則用二維陣列可讀性會較高。

1.建立一個全域的namespace讓所有的cpp都能夠呼叫使用,並將要宣告的別名都實現在general。
註:簡易解說extern,主要用來讓有include此標頭檔的文件能呼叫做使用。

標頭檔:general.h

#pragma once
#ifndef GENERAL_H
#define GENERAL_H
#include <fstream>
#include <string>
#include <algorithm>

typedef unsigned char UCHAE;
typedef const unsigned char C_UCHAE;
typedef const char C_CHAE;
typedef unsigned __int32 UINT32;
typedef const unsigned __int32 C_UINT32;
typedef const __int32 C_INT32;
typedef const double C_DOUBLE;

namespace MNDT {

	extern void Write(C_UCHAE* str);

	extern char* LOG_FILE;
}
#endif // !GENERAL_H

實作檔:general.cpp

#include "general.h"

extern void MNDT::Write(C_UCHAE* str)
{
	std::fstream fwLog;

	fwLog.open(MNDT::LOG_FILE, std::ios::app);
	fwLog << str;
	fwLog.close();
}

extern char* MNDT::LOG_FILE = "D:\\Log.txt";

2.建立一個Library.h標頭檔和Library.cpp實作檔,建立一個class名為Library,宣告一個色彩空間轉換函數ChangeColor和一個清單ColerType。

ChangeColor參數:
https://ithelp.ithome.com.tw/upload/images/20181003/20110564A6UyinLjx3.png

標頭檔:Library.h

#pragma once
#ifndef LIBRARY_H
#define LIBRARY_H
#include "general.h"

enum ColerType
{
	BGR2GRAY_8BIT,
	BGR2HSV,
	HSV2BGR,
	BGR2YCbCr,
	YCbCr2BGR,
};

class Library
{
public:
	Library();

	~Library();
    
	/*
		ChangeColor Parameter:
		src			= source of image
		pur			= purpose of image
		width		= Image's width
		height		= Image's height
		type		= change type
	*/
	void ChangeColor(C_UCHAE* src, UCHAE* pur
		, C_UINT32 width, C_UINT32 height
		, C_UINT32 type);
private:
};

#endif // !LIBRARY_H

實作檔:Library.cpp

#include "Library.h"

Library::Library()
{
}

Library::~Library()
{
}

void Library::ChangeColor(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height
	, C_UINT32 type)
{
	switch (type)
	{
	}
}

灰階影像

灰階主要將RGB三個通道轉為一個通道,主要使用YCbCr的Y(亮度)來做處理,為了加快運算所以將小數點轉為整數,將現在數值乘上2^16次方,計算完後再使用位移這樣就能較快速計算。
公式為[1]:
Y = R * 0.299 + G * 0.587 + B * 0.114。

步驟

  • 建立函數BGR2Gray8Bit在Library.h和Library.cpp。
  • ColorType加入BGR2GRAY_8BIT。

標頭檔:加入Library.h的private

	void BGR2Gray8Bit(C_UCHAE* src, UCHAE* pur
		, C_UINT32 width, C_UINT32 height);

實作檔:函數加入Library.cpp、ChangeColor

  • ChangeColor:
case ColerType::BGR2GRAY_8BIT:
		BGR2Gray8Bit(src, pur, width, height);
		break;
  • BGR2Gray8Bit:
// 灰階
// R 0.299 ≈ 19595
// G 0.587 ≈ 38469
// B 0.114 ≈ 7472 (進位 反推回去比7471接近)
// 為了快速運算(整數運算+位移) 先將數值左位移16次冪 65536
void Library::BGR2Gray8Bit(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height)
{
	C_UCHAE* purEnd = pur + width * height + 1;

	while (pur < purEnd)
	{
		C_UINT32 B = *src;
		src++;

		C_UINT32 G = *src;
		src++;

		C_UINT32 R = *src;
		src++;

		C_UCHAE pix = static_cast<UCHAE>((R * 19595 + G * 38469 + B * 7472) >> 16);
		*pur = pix;
		pur++;
	}

}

  

YCbCr影像

[2]提到Y為亮度,Cb和Cr為色度,YCbCr還有許多用途若有興趣可以前往維基百科觀看一些用途,這裡主要實作公式為主,這裡也是為了加快速度先乘上2^8在位移回去。
公式為[2]:

  • RGB轉YCbCr
    Y = (66 * R + 129 * G + 25 * B + 128) >> 8 + 16。
    Cb = (-38 * R - 74 * G + 112 * B + 128) >> 8 + 128。
    Cr = (112 * R - 94 * G - 18 * B + 128) >> 8 + 128。
  • YCbCr轉RBG
    R = (298 * (Y - 16) + 409 * (Cr - 128) + 128) >> 8
    G = (298 * (Y - 16) - 100 * (Cb - 128) - 208 * (Cr - 128) + 128) >> 8
    B = (298 * (Y - 16) + 516 * (Cr - 128) + 128) >> 8

因為後面會應用在圖片上做為亮度與色度可調性,所以將BGR轉YCbCr和YCbCr轉BGR的單像素抓出來做函數。(應用為BGR轉YCbCr調整再轉回BGR所以拉出來會減少撰寫程式的多寡)
步驟

  • 建立函數BGR2YCbCr、SetBGR2YCbCr、YCbCr2BGR、SetYCbCr2BGR在Library.h和Library.cpp。
  • ColorType加入BGR2YCbCr、YCbCr2BGR。

標頭檔:加入Library.h的private

	void BGR2YCbCr(C_UCHAE* src, UCHAE* pur
		, C_UINT32 width, C_UINT32 height);

	void SetBGR2YCbCr(int32_t* const ycbcr
		, C_INT32& B, C_INT32& G, C_INT32& R);

	void YCbCr2BGR(C_UCHAE* src, UCHAE* pur
		, C_UINT32 width, C_UINT32 height);

	void SetYCbCr2BGR(int32_t* const bgr
		, C_INT32& Y, C_INT32& Cb, C_INT32& Cr);

實作檔:函數加入Library.cpp、ChangeColor

  • ChangeColor:
	case ColerType::BGR2YCbCr:
		BGR2YCbCr(src, pur, width, height);
		break;
	case ColerType::YCbCr2BGR:
		YCbCr2BGR(src, pur, width, height);
		break;
  • BGR2YCbCr、SetBGR2YCbCr:
// BGR轉YCbCr
// Y = (66 * R + 129 * G + 25 * B + 128) >> 8 + 16
// Cb = (-38 * R - 74 * G + 112 * B + 128) >> 8 + 128
// Cr = (112 * R - 94 * G - 18 * B + 128) >> 8 + 128
// 為了快速運算(整數運算+位移) 先將數值左位移16次冪 65536
void Library::BGR2YCbCr(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height)
{
	C_UCHAE* purEnd = pur + width * 3 * height + 1;

	while (pur < purEnd)
	{
		C_INT32 B = *src;
		src++;

		C_INT32 G = *src;
		src++;

		C_INT32 R = *src;
		src++;

		int32_t ycbcr[3];
		SetBGR2YCbCr(ycbcr, B, G, R);

		ycbcr[0] = (ycbcr[0] < 0 ? 0 : (ycbcr[0] > 255 ? 255 : ycbcr[0]));
		ycbcr[1] = (ycbcr[1] < 0 ? 0 : (ycbcr[1] > 255 ? 255 : ycbcr[1]));
		ycbcr[2] = (ycbcr[2] < 0 ? 0 : (ycbcr[2] > 255 ? 255 : ycbcr[2]));

		*pur = static_cast<UCHAE>(ycbcr[0]);
		pur++;
		*pur = static_cast<UCHAE>(ycbcr[1]);
		pur++;
		*pur = static_cast<UCHAE>(ycbcr[2]);
		pur++;
	}
}

void Library::SetBGR2YCbCr(int32_t* const ycbcr
	, C_INT32& B, C_INT32& G, C_INT32& R)
{
	*ycbcr = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
	*(ycbcr + 1) = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
	*(ycbcr + 2) = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
}
  • YCbCr2BGR、SetYCbCr2BGR:
// YCbCr轉BGR
// R = (298 * (Y - 16) + 409 * (Cr - 128) + 128) >> 8
// G = (298 * (Y - 16) - 100 * (Cb - 128) - 208 * (Cr - 128) + 128) >> 8
// B = (298 * (Y - 16) + 516 * (Cr - 128) + 128) >> 8
// 為了快速運算(整數運算+位移) 先將數值左位移16次冪 65536
void Library::YCbCr2BGR(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height)
{
	C_UCHAE* purEnd = pur + width * 3 * height + 1;

	while (pur < purEnd)
	{
		C_INT32 Y = *src;
		src++;

		C_INT32 Cb = *src;
		src++;

		C_INT32 Cr = *src;
		src++;

		int32_t bgr[3];
		SetYCbCr2BGR(bgr, Y, Cb, Cr);
		bgr[0] = (bgr[0] < 0 ? 0 : (bgr[0] > 255 ? 255 : bgr[0]));
		bgr[1] = (bgr[1] < 0 ? 0 : (bgr[1] > 255 ? 255 : bgr[1]));
		bgr[2] = (bgr[2] < 0 ? 0 : (bgr[2] > 255 ? 255 : bgr[2]));

		*pur = static_cast<UCHAE>(bgr[0]);
		pur++;
		*pur = static_cast<UCHAE>(bgr[1]);
		pur++;
		*pur = static_cast<UCHAE>(bgr[2]);
		pur++;
	}
}

void Library::SetYCbCr2BGR(int32_t* const bgr
	, C_INT32& Y, C_INT32& Cb, C_INT32& Cr)
{
	C_INT32 fixY = Y - 16;
	C_INT32 fixCb = Cb - 128;
	C_INT32 fixCr = Cr - 128;

	*bgr = (298 * fixY + 516 * fixCb + 128) >> 8;
	*(bgr + 1) = (298 * fixY - 100 * fixCb - 208 * fixCr + 128) >> 8;
	*(bgr + 2) = (298 * fixY + 409 * fixCr + 128) >> 8;
}

HSV

HSV為一個360度色彩空間,[3]解釋為H為色相、S為飽和度、V為明度,詳細介紹至[3]觀看。HSV公式較上面兩個多,這裡會推倒H轉回去RGB公式。
HSV公式為:
RGB轉HSV
MAX(R, G, B) MIN(R, G, B)
300° ~ 60° R 中間值 0°
60° ~ 180° G 中間值 120°
180° ~ 300° B 中間值 240°

H = 0, S = 0 條件:MAX == MIN

H 公式
60° * (G - B) / (MAX - MIN) 條件:if MAX == R && G >= B
60° * (G - B) / (MAX - MIN) + 360° 條件:if MAX == R && G < B
60° * (B - R) / (MAX - MIN) + 120° 條件:if MAX == G
60° * (R - G) / (MAX - MIN) + 240° 條件:if MAX == B

S 公式
(MAX - MIN) / MAX * 100(轉百分比整數)

V 公式
MAX / 255 * 100(轉百分比整數)

HSV轉RGB
H = H * 2.0; // 修正到原先H
S = S / 255.0; // 修正到原先S
offset = H % 60° // 取得不滿60度的剩餘角度
MAX = V
MIN = V * (1 - S)
q = -(offset - 60°) * (V - MIN) / 60° + MIN;
t = offset * (V - MIN) / 60° + MIN;

position = H / 60 // 取得目前在六等分的哪個位置

R = V, G = t, B = MIN 條件:if position == 0
R = q, G = V, B = MIN 條件:if position == 1
R = MIN, G = V, B = t 條件:if position == 2
R = MIN, G = q, B = V 條件:if position == 3
R = t, G = MIN, B = V 條件:if position == 4
R = V, G = MIN, B = q 條件:if position == 5

H轉回RGB推導
1.將H所有可能先列出來(6個角度區域),直接帶入原先H公式並列出角度區域。
offset = (g - min) / (max - min) * 60 0~60
offset = (r - min) / (max - min) * 60 240~300
offset = (b - min) / (max - min) * 60 120~180

// 原先計算出來是負數才會產生此角度,因此原本角度為-60。
offset - 60 = (min - r) / (max - min) * 60 60~120
offset - 60 = (min - g) / (max - min) * 60 180~240
offset - 60 = (min - b) / (max - min) * 60 270~360

2.由上述可知道求rgb知道公式分為兩種,因此推導2個公式即可,最後依照角度區域放入即可。

  • offset = (g - MIN) / (MAX - MIN) * 60
    offset * (MAX - MIN) / 60 = g - MIN
    offset * (MAX - MIN) / 60 + MIN = g
    第一個公式為offset * (MAX - MIN) + MIN

  • offset - 60 = (MIN - r) / (MAX - MIN) * 60
    (offset - 60) * (MAX - MIN) / 60 = MIN - r
    -(offset - 60) * (MAX - MIN) / 60 + MIN = r
    第二個公式為-(offset - 60) * (MAX - MIN) / 60 + MIN

因為後面會應用在圖片上做為亮度與色度可調性,所以將BGR轉HSV和HSV轉BGR的單像素抓出來做函數。(應用為BGR轉HSV調整再轉回BGR所以拉出來會減少撰寫程式的多寡)
步驟

  • 建立函數BGR2HSV、SetBGR2HSV、HSV2BGR、SetHSV2BGR在Library.h和Library.cpp。
  • ColorType加入BGR2HSV、HSV2BGR。

標頭檔:加入Library.h的private

	void BGR2HSV(C_UCHAE* src, UCHAE* pur
		, C_UINT32 width, C_UINT32 height);

	void SetBGR2HSV(double* const hsv
		, C_DOUBLE& B, C_DOUBLE& G, C_DOUBLE& R);

	void HSV2BGR(C_UCHAE* src, UCHAE* pur
		, C_UINT32 width, C_UINT32 height);

	void SetHSV2BGR(double* const bgr
		, C_DOUBLE& H, C_DOUBLE& S, C_DOUBLE& V);

實作檔:函數加入Library.cpp、ChangeColor

  • ChangeColor:
    case ColerType::BGR2HSV:
		BGR2HSV(src, pur, width, height);
		break;
	case ColerType::HSV2BGR:
		HSV2BGR(src, pur, width, height);
		break;
  • BGR2HSV、SetBGR2HSV
void Library::BGR2HSV(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height)
{
	C_UCHAE* purEnd = pur + width * 3 * height + 1;

	while (pur < purEnd)
	{
		C_DOUBLE B = *src;
		src++;

		C_DOUBLE G = *src;
		src++;

		C_DOUBLE R = *src;
		src++;

		double hsv[3];

		SetBGR2HSV(hsv, B, G, R);

		*pur = static_cast<UCHAE>(hsv[0]);
		pur++;
		*pur = static_cast<UCHAE>(hsv[1]);
		pur++;
		*pur = static_cast<UCHAE>(hsv[2]);
		pur++;
	}
}

void Library::SetBGR2HSV(double* const hsv
	, C_DOUBLE& B, C_DOUBLE& G, C_DOUBLE& R)
{

	C_DOUBLE max = std::max(std::max(R, G), B);
	C_DOUBLE min = std::min(std::min(R, G), B);

	*(hsv + 2) = max;

	C_DOUBLE difference = max - min;

	if (max != min)
	{
		if (max == R && G >= B)
		{
			*hsv = (G - B) / difference;
		}
		else if (max == R && G < B)
		{
			*hsv = (G - B) / difference + 6.0;
		}
		else if (max == G)
		{
			*hsv = (B - R) / difference + 2.0;
		}
		else if (max == B)
		{
			*hsv = (R - G) / difference + 4.0;
		}

		// 配合RGB正規化H和S
		*hsv *= 30.0;
		*(hsv + 1) = max == 0 ? 0 : (difference / max) * 255.0;
	}
}
  • HSV2BGR、SetHSV2BGR
void Library::HSV2BGR(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height)
{
	C_UCHAE* purEnd = pur + width * 3 * height + 1;

	while (pur < purEnd)
	{
		C_DOUBLE H = static_cast<double>(*src);
		src++;

		C_DOUBLE S = static_cast<double>(*src);
		src++;

		C_DOUBLE V = static_cast<double>(*src);
		src++;

		double bgr[3];
		SetHSV2BGR(bgr, H, S, V);

		*pur = static_cast<UCHAE>(bgr[0]);
		pur++;
		*pur = static_cast<UCHAE>(bgr[1]);
		pur++;
		*pur = static_cast<UCHAE>(bgr[2]);
		pur++;
	}
}

void Library::SetHSV2BGR(double* const bgr
	, C_DOUBLE& H, C_DOUBLE& S, C_DOUBLE& V)
{
	C_DOUBLE fixH = H * 2.0;
	C_DOUBLE fixS = S / 255.0;

	C_UINT32 position = static_cast<int32_t>(fixH) / 60;
	C_DOUBLE offset = static_cast<int32_t>(fixH) % 60;

	// s = (max - min) / max,求min
	C_DOUBLE min = V * (1.0 - fixS);

	// -(f - 1) * (max - min) + min = g; 120.240.360
	C_DOUBLE q = -(offset - 60.0) * (V - min) / 60.0 + min;

	// f * (max - min) + min = g; 60.180.300
	C_DOUBLE t = offset * (V - min) / 60.0 + min;

	double R = 0;
	double G = 0;
	double B = 0;

	switch (position)
	{
	case 0:
		R = V;
		G = t;
		B = min;
		break;
	case 1:
		R = q;
		G = V;
		B = min;
		break;
	case 2:
		R = min;
		G = V;
		B = t;
		break;
	case 3:
		R = min;
		G = q;
		B = V;
		break;
	case 4:
		R = t;
		G = min;
		B = V;
		break;
	case 5:
		R = V;
		G = min;
		B = q;
		break;
	}
	*bgr = B;
	*(bgr + 1) = G;
	*(bgr + 2) = R;
}

使用空間處理影像

接著使用上述兩種空間去做處理,主要用%來去計算,原理為單純計算離0或255的距離以%做簡單轉換,C#內預設50%。

建立函數AdjustmentHSV、AdjustmentYCbCr在Library.h和Library.cpp。

標頭檔:加入Library.h的public

	/*
		AdjustmentHSV Parameter:
		src		= source of image
		pur		= purpose of image
		H		= H's %
		S		= S's %
		V		= V's %
	*/
	void AdjustmentHSV(C_UCHAE* src, UCHAE* pur
		, C_UINT32 width, C_UINT32 height
		, C_INT32 H, C_INT32 S, C_INT32 V);

	/*
		AdjustmentYCbCr Parameter:
		src		= source of image
		pur		= purpose of image
		Y		= Y's %
		Cb		= Cb's %
		Cr		= Cr's %
	*/
	void AdjustmentYCbCr(C_UCHAE* src, UCHAE* pur
		, C_UINT32 width, C_UINT32 height
		, C_INT32 Y, C_INT32 Cb, C_INT32 Cr);

實作檔:加入函數到Library.cpp。

  • AdjustmentHSV
void Library::AdjustmentHSV(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height
	, C_INT32 H, C_INT32 S, C_INT32 V)
{
	C_UCHAE* purEnd = pur + width * 3 * height + 1;

	while (pur < purEnd)
	{
		C_DOUBLE B = static_cast<double>(*src);
		src++;

		C_DOUBLE G = static_cast<double>(*src);
		src++;

		C_DOUBLE R = static_cast<double>(*src);
		src++;

		double hsv[3];
		double bgr[3];

		SetBGR2HSV(hsv, B, G, R);

		if (H < 0)
		{
			hsv[0] = hsv[0] * static_cast<double>(100 + H) / 100.0;
		}
		else
		{
			hsv[0] = hsv[0] + (180 - hsv[0]) * static_cast<double>(H) / 100.0;
		}

		if (S < 0)
		{
			hsv[1] = hsv[1] * static_cast<double>(100.0 + S) / 100.0;
		}
		else
		{
			hsv[1] = hsv[1] + (255.0 - hsv[1]) * static_cast<double>(S) / 100.0;
		}

		if (V < 0)
		{
			hsv[2] = hsv[2] * static_cast<double>(100.0 + V) / 100.0;
		}
		else
		{
			hsv[2] = hsv[2] + (255.0 - hsv[2]) * static_cast<double>(V) / 100.0;
		}
		SetHSV2BGR(bgr, hsv[0], hsv[1], hsv[2]);

		*pur = static_cast<UCHAE>(bgr[0]);
		pur++;
		*pur = static_cast<UCHAE>(bgr[1]);
		pur++;
		*pur = static_cast<UCHAE>(bgr[2]);
		pur++;
	}
}
  • AdjustmentYCbCr
void Library::AdjustmentYCbCr(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height
	, C_INT32 Y, C_INT32 Cb, C_INT32 Cr)
{
	C_UCHAE* purEnd = pur + width * 3 * height + 1;

	while (pur < purEnd)
	{
		C_INT32 B = *src;
		src++;

		C_INT32 G = *src;
		src++;

		C_INT32 R = *src;
		src++;

		int32_t ycbcr[3];
		int32_t bgr[3];

		SetBGR2YCbCr(ycbcr, B, G, R);

		if (Y < 0)
		{
			ycbcr[0] = static_cast<int32_t>(ycbcr[0] * static_cast<double>(100 + Y) / 100.0);
		}
		else
		{
			ycbcr[0] = static_cast<int32_t>(ycbcr[0] + (255 - ycbcr[0]) * static_cast<double>(Y) / 100.0);
		}

		if (Cb < 0)
		{
			ycbcr[1] = static_cast<int32_t>(ycbcr[1] * static_cast<double>(100 + Cb) / 100.0);
		}
		else
		{
			ycbcr[1] = static_cast<int32_t>(ycbcr[1] + (255 - ycbcr[1]) * static_cast<double>(Cb) / 100.0);
		}

		if (Cr < 0)
		{
			ycbcr[2] = static_cast<int32_t>(ycbcr[2] * static_cast<double>(100.0 + Cr) / 100.0);
		}
		else
		{
			ycbcr[2] = static_cast<int32_t>(ycbcr[2] + (255.0 - ycbcr[2]) * static_cast<double>(Cr) / 100.0);
		}

		SetYCbCr2BGR(bgr, ycbcr[0], ycbcr[1], ycbcr[2]);

		bgr[0] = (bgr[0] < 0 ? 0 : (bgr[0] > 255 ? 255 : bgr[0]));
		bgr[1] = (bgr[1] < 0 ? 0 : (bgr[1] > 255 ? 255 : bgr[1]));
		bgr[2] = (bgr[2] < 0 ? 0 : (bgr[2] > 255 ? 255 : bgr[2]));

		*pur = static_cast<UCHAE>(bgr[0]);
		pur++;
		*pur = static_cast<UCHAE>(bgr[1]);
		pur++;
		*pur = static_cast<UCHAE>(bgr[2]);
		pur++;
	}
}

可視化

接著主要將剛剛實現的原始碼可視化,首先建立C++的接口,在到C#導入,這上一篇有講解過,因程式碼不好貼上來,所以這裡以色彩轉換函數為例子,最後有副上完整的程式碼。

1.MNDTLibrary.cpp加入後編譯dll。

extern "C" MNDTLIBRARY_API void mndtChangeColor(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height
	, C_UINT32 type)
{
	Library lib;
	lib.ChangeColor(src, pur, width, height, type);
}

2.MNDTLibrary.cs的namespace內加入清單(與C++清單一樣)。

    public enum ColerType : int
    {
        BGR2GRAY_8BIT = 0,
        BGR2HSV = 1,
        HSV2BGR = 2,
        BGR2YCbCr = 3,
        YCbCr2BGR = 4
    }

3.MNDTLibrary.cs的class加入函數,這裡分兩種一個為8bit一個為24bit使用函數。

        [DllImport(DLL_PATH)]
        unsafe private static extern void mndtChangeColor(IntPtr src, IntPtr pur, int width, int height, int type);

        public Bitmap Change8BitColor(Bitmap srcImage, ColerType type)
        {
            Bitmap purImage = new Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format8bppIndexed);
            Rectangle size = new Rectangle(0, 0, srcImage.Width, srcImage.Height);
            BitmapData srcData = srcImage.LockBits(size, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            BitmapData purData = purImage.LockBits(size, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);

            unsafe
            {
                IntPtr srcPtr = srcData.Scan0;
                IntPtr purPtr = purData.Scan0;
                mndtChangeColor(srcPtr, purPtr, srcImage.Width, srcImage.Height, (int)type);
            }

            purImage.Palette = _colorPalette;
            srcImage.UnlockBits(srcData);
            purImage.UnlockBits(purData);

            return purImage;
        }

        public Bitmap ChangeColor(Bitmap srcImage, ColerType type)
        {
            Bitmap purImage = new Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format24bppRgb);
            Rectangle size = new Rectangle(0, 0, srcImage.Width, srcImage.Height);
            BitmapData srcData = srcImage.LockBits(size, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            BitmapData purData = purImage.LockBits(size, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            unsafe
            {
                IntPtr srcPtr = srcData.Scan0;
                IntPtr purPtr = purData.Scan0;
                mndtChangeColor(srcPtr, purPtr, srcImage.Width, srcImage.Height, (int)type);
            }
            
            srcImage.UnlockBits(srcData);
            purImage.UnlockBits(purData);

            return purImage;
        }

4.圖片轉8bit灰階呼叫範例

MNDTLibrary _lib = new MNDTLibrary();
pic_pur.Image = _lib.Change8BitColor(_fileImage, ColerType.BGR2GRAY_8BIT);

結果圖
https://ithelp.ithome.com.tw/upload/images/20181003/20110564KRjlsUSrEu.png

MNDTVisualization C#程式碼

結論

這大概算是把之前寫的影像處理重新整理一次程式碼,因為接著還要加入Adaboost和CNN等等東西,順便作筆記讓自己未來能回來翻翻。
在程式碼部分效能不是最好的,有些地方為了精準的運算我都將它改成double,比較原先int或char運算效能就有差別了,但若不這樣做圖片損失會較嚴重,而有的地方還能簡化,但可讀性會降低許多,對於程式碼或觀念有誤或更好方法歡迎下方留言提供。

參考文獻

[1]WILLIE SONG.(2011) RGB 轉灰階公式 from: http://droidparadise.blogspot.com/2011/10/rgb_18.html.(2018.09.30)
[2]維基百科.(2018). YCbCr from: https://en.wikipedia.org/wiki/YCbCr.(2018.09.30)
[3]維基百科.(2018). HSL和HSV色彩空間 from: https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4.(2018.09.30)


尚未有邦友留言

立即登入留言