專案名稱: SpeedUpPixelAcess
Mask: 通常為一張黑白圖片,也就是二值化圖片,用來擷取感興趣的物件(如膚色偵測)或是圖形去背
利用這些黑白的圖片跟原始圖片做對應,白色255的部份為顯示出來的區域,黑色為0的部份為不顯示出來的區域,
讀入一張彩色影像img1
讀入一張遮罩影像mask
將img1套用遮罩mask, 可以得到結果影像img2
其中來源影像和遮罩影像大小必須一樣
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兩張對不起來
將mask進行二值化
CvInvoke.Threshold(mask, mask, 0, 255, ThresholdType.Binary);
img1.CopyTo(img2, mask);
========================================
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;
}
}
========================================
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<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;
}
或是也可以將範圍內的遮罩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;
}
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;
}
關於Affine Transformation可以參考這篇: 仿射变换
(1) A矩陣描述旋轉運動: 其中θ為旋轉角度
(2) B矩陣描述平移運動: h,k分別為水平和垂直移動量
(3) 縮放: 可以在M前加入一個增益量, 或是由M每個元素吸收
映射轉換T
上式我們通常使用 2X3 矩陣M來表示仿射轉換,
其中A, B, M表示如下:
image1 –> image2存在一個M矩陣
其原理是利用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的數值變化
得到transMat ( M ), 利用WarpAffine指令將來源影像(imgSrc)映射到結果影像(imgDst)
CvInvoke.WarpAffine(imgSrc, imgDst, transMat, imgSrc.Size)
==========================================
上面我們已經會旋轉一張影像, 同樣的,
我們也可以將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;
}
結果影像m_img2Mat
參考資料:
留言列表