close

專案名稱: SpeedUpPixelAcess

Mask: 通常為一張黑白圖片,也就是二值化圖片,用來擷取感興趣的物件(如膚色偵測)或是圖形去背

利用這些黑白的圖片跟原始圖片做對應,白色255的部份為顯示出來的區域,黑色為0的部份為不顯示出來的區域,

讀入一張彩色影像img1

image

讀入一張遮罩影像mask

image

將img1套用遮罩mask, 可以得到結果影像img2

其中來源影像和遮罩影像大小必須一樣

image



using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
Mat img1 = CvInvoke.Imread(ofd.FileName, LoadImageType.Color);
Size sz = img1.Size;
Mat img2 = new Mat(sz, DepthType.Cv8U, 3);
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
Mat mask = CvInvoke.Imread(ofd.FileName, LoadImageType.Grayscale);
try
{
//CvInvoke.cvCopy(img1, img2, mask);
img1.CopyTo(img2, mask);
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
}
CvInvoke.Imshow("img1", img1);
CvInvoke.Imshow("mask", mask);
CvInvoke.Imshow("img2", img2);
}
}

放大觀察mask和img2, 發現mask並非單純黑白圖, 而是灰階圖(0~255),

因此mask和img2兩張對不起來

image  image

將mask進行二值化

CvInvoke.Threshold(mask, mask, 0, 255, ThresholdType.Binary);
img1.CopyTo(img2, mask);

image image

========================================

CvInvoke.Circle Method

using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
Mat img1 = CvInvoke.Imread(ofd.FileName, LoadImageType.Color);
Size sz = img1.Size;
Mat mask = new Mat(sz, DepthType.Cv8U, 1);
Mat img2 = new Mat(sz, DepthType.Cv8U, 3);
Point center = new Point(sz.Width/2, sz.Height/2); // 圓形遮罩中心
int radius = Math.Min(sz.Width, sz.Height)/2; // 圓形遮罩半徑大小
MCvScalar color = new MCvScalar(255, 255, 255); // 遮罩顏色
//CvInvoke.Circle(mask, center, radius, color, -1, Emgu.CV.CvEnum.LineType.AntiAlias, 0);
CvInvoke.Circle(mask, center, radius, color, -1);
img1.CopyTo(img2, mask);
imageBox1.Image = img1;
imageBox2.Image = mask;
imageBox3.Image = img2;
}
}

image image

========================================

CvInvoke.Rectangle Method

using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
Mat img1 = CvInvoke.Imread(ofd.FileName, LoadImageType.Color);
Size sz = img1.Size;
Mat mask = new Mat(sz, DepthType.Cv8U, 1);
Mat img2 = new Mat(sz, DepthType.Cv8U, 3);
Point topLeftPoint = new Point(sz.Width / 3, sz.Height / 3);// 矩形遮罩左上角
int wid = Math.Min(sz.Width, sz.Height) / 3; // 矩形遮罩半徑大小
MCvScalar color = new MCvScalar(255, 255, 255); // 遮罩顏色
//CvInvoke.Circle(mask, center, radius, color, -1, Emgu.CV.CvEnum.LineType.AntiAlias, 0);
Rectangle rect = new Rectangle(topLeftPoint, new Size(wid, wid) );
CvInvoke.Rectangle(mask, rect, color, -1);
//CvInvoke.Circle(mask, center, radius, color, -1);
img1.CopyTo(img2, mask);
imageBox1.Image = img1;
imageBox2.Image = mask;
imageBox3.Image = img2;
}
}

image image

========================================

Image<Bgr, byte> m_img1 = null;
Image<Gray, byte> m_mask;
Image<Bgr, byte> m_img2 = null;

bool m_imgLoaded = false;

Bgr lower = new Bgr(0, 0, 0);
Bgr higher = new Bgr(255, 255, 255);

利用InRange自動產生遮罩m_mask,

來源影像(m_img1)套用遮罩後得到結果影像(m_img2)

private void InRangeSub()
{
if (m_imgLoaded == false) return;
//CvInvoke.InRange(m_img, lower, higher, m_mask);
m_mask = m_img1.InRange(lower, higher);
m_img2 = m_img1.Copy(m_mask);
imageBox2.Image = m_mask;
imageBox3.Image = m_img2;
}

image

或是也可以將範圍內的遮罩m_mask或是m_mask.Not()塗上特定顏色

像是這篇

Replace a range of colors with a specific color

private void InRangeSub()
{
if (m_imgLoaded == false) return;
//CvInvoke.InRange(m_img, lower, higher, m_mask);
m_mask = m_img1.InRange(lower, higher);
m_img2 = m_img1.Copy(m_mask);
m_img2.SetValue(new Bgr(255, 255, 0), m_mask.Not());
imageBox2.Image = m_mask;
imageBox3.Image = m_img2;
}

image

vScrollBar的顏色上下限程式如下:

private void vScrollBar1_ValueChanged(object sender, EventArgs e)
{
textBox1.Text = vScrollBar1.Value.ToString();
lower.Red = Convert.ToInt16(textBox1.Text);
InRangeSub();
}

private void vScrollBar2_ValueChanged(object sender, EventArgs e)
{
textBox2.Text = vScrollBar2.Value.ToString();
lower.Green = Convert.ToInt16(textBox2.Text);
InRangeSub();
}

private void vScrollBar3_ValueChanged(object sender, EventArgs e)
{
textBox3.Text = vScrollBar3.Value.ToString();
lower.Blue = Convert.ToInt16(textBox3.Text);
InRangeSub();
}

private void vScrollBar4_ValueChanged(object sender, EventArgs e)
{
textBox4.Text = vScrollBar4.Value.ToString();
higher.Red = Convert.ToInt16(textBox4.Text);
InRangeSub();
}

private void vScrollBar5_ValueChanged(object sender, EventArgs e)
{
textBox5.Text = vScrollBar5.Value.ToString();
higher.Green = Convert.ToInt16(textBox5.Text);
InRangeSub();
}

private void vScrollBar6_ValueChanged(object sender, EventArgs e)
{
textBox6.Text = vScrollBar6.Value.ToString();
higher.Blue = Convert.ToInt16(textBox6.Text);
InRangeSub();
}

============================================

如果要將rectangle ROI旋轉一個角度, 有辦法做到嗎?

回答這個問題可以先想一個更簡單的問題, 如何旋轉一張影像?

可以參考這篇OpenCV: how to rotate IplImage?

所謂的仿射轉換 (affine transformation), 包含旋轉,平移和縮放

轉換後線條仍是線條, 而且平行線仍然是平行線

srcCenter: 旋轉中心

transMat: 轉換矩陣

GetRotationMatrix2D:計算以來源影像中心點(srcCenter),

旋轉某一個角度(angle)和給定縮放比例(scale),

將轉換矩陣輸出至transMat變數

WarpAffine: 輸入來源影像(imgSrc)及剛才得到的

轉換矩陣(transMat)和來源影像大小(imgSrc.Size),

則可得到幾何轉換後的輸出影像(imgDst)

private Mat RotateImage(Mat imgSrc, double angle)
{
PointF srcCenter = new PointF(imgSrc.Cols / 2, imgSrc.Rows / 2);
Mat transMat = new Mat();
double scale = 1.0;
CvInvoke.GetRotationMatrix2D(srcCenter, angle, scale, transMat);
Mat imgDst = new Mat();
CvInvoke.WarpAffine(imgSrc, imgDst, transMat, imgSrc.Size);
return imgDst;
}

輸入不同角度(angle)則可以得到不同角度幾何轉換後的影像(imgRotated)

private void RotateImageSub(double angle)
{
if (m_imgLoaded == false) return;
Mat imgRotated = RotateImage(m_img1.Mat, angle);
imageBox2.Image = imgRotated;
}

image

關於Affine Transformation可以參考這篇: 仿射变换

(1) A矩陣描述旋轉運動:  其中θ為旋轉角度

image

(2) B矩陣描述平移運動:  h,k分別為水平和垂直移動量

image

(3) 縮放: 可以在M前加入一個增益量, 或是由M每個元素吸收

映射轉換T

image

上式我們通常使用 2X3 矩陣M來表示仿射轉換,

image

其中A, B, M表示如下:

image

image

image1 –> image2存在一個M矩陣

image

其原理是利用image1三個座標點配對image2上的三個座標點,

利用GetRotationMatrix2D 給定旋轉角度(angle)和縮放比例(scale), 

自動解出M六組未知數存到transMat

CvInvoke.GetRotationMatrix2D(srcCenter, angle, scale, transMat)

private Mat RotateImage(Mat imgSrc, double angle)
{
PointF srcCenter = new PointF(imgSrc.Cols / 2, imgSrc.Rows / 2);
Matrix<double> transMat = new Matrix<double>(2,3);
double scale = 1.0;
CvInvoke.GetRotationMatrix2D(srcCenter, angle, scale, transMat);

int rows, cols;
rows = transMat.Rows;
cols = transMat.Cols;
dataGridView1.RowCount = rows;
dataGridView1.ColumnCount= cols;
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
dataGridView1[j, i].Value = transMat.Data[i, j].ToString();

Mat imgDst = new Mat();
CvInvoke.WarpAffine(imgSrc, imgDst, transMat, imgSrc.Size);
return imgDst;
}

假設旋轉一張影像, 觀察不同旋轉角度, M2x3的數值變化

旋轉角度 θ=0, M= image

旋轉角度θ=90, M= image

旋轉角度θ=180, M=image

得到transMat ( M ), 利用WarpAffine指令將來源影像(imgSrc)映射到結果影像(imgDst)

CvInvoke.WarpAffine(imgSrc, imgDst, transMat, imgSrc.Size)

image

==========================================

上面我們已經會旋轉一張影像, 同樣的,

我們也可以將rectangle ROI遮罩當作一般影像,

給予特定的角度進行旋轉

以下程式碼是根據來源影像大小回傳矩形遮罩

private Mat CreateRectROI(Mat img)
{
Size sz = img.Size;
Mat mask = new Mat(sz, DepthType.Cv8U, 1);
Point topLeftPoint = new Point(sz.Width / 3, sz.Height / 3);// 矩形遮罩左上角
int wid = Math.Min(sz.Width, sz.Height) / 3; // 矩形遮罩半徑大小
MCvScalar color = new MCvScalar(255, 255, 255); // 遮罩顏色
Rectangle rect = new Rectangle(topLeftPoint, new Size(wid, wid));
CvInvoke.Rectangle(mask, rect, color, -1);
return mask;
}

產生矩形遮罩m_rectMask, 並旋轉遮罩angle角度

複製(CopyTo)來源影像m_img1.Mat 同時套用旋轉後的遮罩m_rectMask

private void RotateRectROISub(double angle)
{
if (m_imgLoaded == false) return;
m_rectMask = CreateRectROI(m_img1.Mat);
m_rectMask = RotateImage(m_rectMask, angle);
imageBox2.Image = m_rectMask;

m_img2Mat.SetTo(new MCvScalar(0));
m_img1.Mat.CopyTo(m_img2Mat, m_rectMask);
imageBox3.Image = m_img2Mat;
}

image

結果影像m_img2Mat

image



參考資料:

1. 資料結構操作及運算-圖形的Mask遮罩實作
2. [OpenCV] 顏色遮罩 (Color Mask)
3. ROI, always a rectangle?
4. Emgu cv save CircleF as a image
5. How to use CvInRange in Emgu CV
6. RotatedRect ROI in OpenCV
7. Custom ROI (Region Of Interest) with OpenCV
8. How to detect triangle edge in opencv or emgu cv?
9. C++: OpenCV的邊緣地圖approxpolydp(不是輪廓)
10. Contour Identification of an Image using C# and EmguCV
11. OpenCV: how to rotate IplImage?
12. Number of non-zero pixels in a cv::RotatedRect
13. Rotate Opencv Matrix by 90, 180, 270 degrees
14. 仿射变换

15. ENB339 lecture 9: Image geometry and planar homography

arrow
arrow
    全站熱搜

    me1237guy 發表在 痞客邦 留言(0) 人氣()