形态學輪廓提取
- 流程分析 原圖像imgA:
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 噪聲濾除圖像imgB:圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 腐蝕處理圖像imgC:圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 相減操作圖像imgD:圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 二值化處理結果imgE:圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) -
原理分析:
2.1膨脹:求像素的局部最大值
将圖像(或圖像的一部分區域,我們稱之為A)與核(我們稱之為B)進行如下卷積操作:
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 2.2腐蝕:求像素的局部最小值
将圖像(或圖像的一部分區域,我們稱之為A)與核(我們稱之為B)進行如下卷積操作:
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) (膨脹腐蝕原理詳細可參見部落格:http://blog.sina.com.cn/s/blog_6f57a7150100ooin.html 非常感謝部落客的分享。)
2.3噪聲濾除:
開運算: A∘ B=(A⊝B)⊕B
閉運算: A·B=(A⊕B)⊝B
噪聲濾除: {[(A⊝B)⊕B]⊕B}⊝B=(A∘ B)·B
在上圖中,(a)是原圖像,外部有噪聲塊,内部有噪聲孔,(b)為結構元素,尺寸大于所有噪聲塊和噪聲孔,(c)是用(b)去腐蝕(a)的結果,可見,外部的噪聲塊被去除;之後用(b)去膨脹(c)兩次,得到(e),此時已經去除了内部的噪聲孔,在進行一次腐蝕操作,得到和原圖一樣大小的去噪圖像(f)。圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) -
實驗對比:
3.1圖像格式對比:
參數設定:不進行噪聲濾除,腐蝕2次,十字核,尺寸3*3
二值圖像;
灰階圖像:圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) RGB圖像:圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 3.2核樣式對比:
參數設定:RGB圖像,不進行”噪聲濾除”,腐蝕1次,尺寸5*5,二值化門檻值100.
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 3.3核尺寸對比:
參數設定:RGB圖像,不進行”噪聲濾除”,十字型核樣式,腐蝕1次,二值化門檻值100
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 3.4腐蝕次數對比:
參數設定:RGB圖像,不進行”噪聲濾除”,十字型核樣式,尺寸5*5,二值化門檻值100
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 3.5噪聲濾除強度對比:
參數設定: RGB圖像,十字型,腐蝕1次,核樣式,尺寸5*5,二值化門檻值100
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) 3.6對比分析:
圖像格式:RGB格式提取輪廓效果較好,灰階和二值圖像資訊相對有限。
核樣式:十字型效果最佳,矩形和圓形容易造成噪點。
核尺寸:尺寸過小,輪廓提取不完整;尺寸過大,容易出現噪點。
腐蝕次數:腐蝕次數過小,輪廓提取不完整;次數過多,容易出現噪點,且輪廓邊緣強度過大。
噪聲濾除:影響把圖像輪廓深度資訊提取。(标紅區域)
- 代碼:
morphology.h
#pragma once
#include "stdafx.h"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat getKernelMatrix(int shape, Size ksize);//核心矩陣生成
void erodeFun(Mat &src, Mat &dst, Mat kernel, int iterations);//腐蝕操作
void dilateFun(Mat &src, Mat &dst, Mat kernel, int iterations);//膨脹操作
void openFun(Mat &src, Mat &dst, Mat kernel, int iterations);//開操作
void closeFun(Mat &src, Mat &dst, Mat kernel, int iterations);//閉操作
void subtractFun(Mat src, Mat erode_ouput, Mat &externalGradientImg, int threshold);//減操作
morphology.cpp
#include "stdafx.h"
#include "morphology.h"
/*
函數: 擷取核心矩陣
參數: shape核心樣式(矩形: MORPH_RECT 交叉形十字: MORPH_CROSS 橢圓形: MORPH_ELLIPSE)
ksize核心大小
*/
Mat getKernelMatrix(int shape, Size ksize) {
Point center((int)ksize.width / , (int)ksize.height / );
int i, j;
int r = , c = ;//圓參數
double helperR = ;//圓參數
if (ksize == Size(, ))//機關核心都是矩形核心
shape = MORPH_RECT;
if (shape == MORPH_ELLIPSE)//橢圓形核心參數處理
{
r = ksize.height / ;
c = ksize.width / ;
helperR = r ? / ((double)r*r) : ;
}
Mat kernel(ksize, CV_8U);
//行周遊
for (i = ; i < ksize.height; i++)
{
uchar* ptr = kernel.data + i*kernel.step;
int j1 = , j2 = ;
//确定每一行1的區間[j1,j2]
if (shape == MORPH_RECT || (shape == MORPH_CROSS && i == center.y))
j2 = ksize.width;
else if (shape == MORPH_CROSS)
j1 = center.x, j2 = j1 + ;
else
{
int dy = i - r;
if (std::abs(dy) <= r)//圓
{
int dx = saturate_cast<int>(c*sqrt((r*r - dy*dy)*helperR));
j1 = std::max(c - dx, );
j2 = std::min(c + dx + , ksize.width);
}
}
//指派操作
for (j = ; j < j1; j++)
ptr[j] = ;
for (; j < j2; j++)
ptr[j] = ;
for (; j < ksize.width; j++)
ptr[j] = ;
}
return kernel;
}
/*
函數: 腐蝕操作
參數: src:原圖像
dst:結果圖像
kernel:核矩陣
iterations:操作次數
*/
void erodeFun(Mat &src, Mat &dst, Mat kernel, int iterations) {
//參數判定
if (iterations == ) { return; }
if (kernel.cols == && kernel.rows == ){ return; }
Mat_<uchar> kernelMatrix = kernel;
int HALFKERLEN = kernelMatrix.cols / ;//正方形
int KERLEN = kernelMatrix.cols;
Mat pre = src.clone();
Mat next = src.clone();
for (int iterator = ; iterator < iterations; iterator++) {//腐蝕次數
for (int i = ; i<pre.rows; i++)
{
for (int j = ; j < pre.cols; j++)
{
//處理每一個像素值的每一個通道
Vec3b minPixel = pre.at<Vec3b>(i, j);
//周遊核
for (int ki = ; ki < KERLEN; ki++) {
for (int kj = ; kj < KERLEN; kj++) {
if ((int)kernelMatrix.at<uchar>(ki, kj) == ) {//核為1,處理
int pi = ki + i - HALFKERLEN;
int pj = kj + j - HALFKERLEN;
if (pi >= && pi < pre.rows && pj >= && pj < pre.cols)//防止超限
{
if (pre.at<Vec3b>(pi, pj)[] < minPixel[]) {
minPixel[] = pre.at<Vec3b>(pi, pj)[];
}
if (pre.at<Vec3b>(pi, pj)[] < minPixel[]) {
minPixel[] = pre.at<Vec3b>(pi, pj)[];
}
if (pre.at<Vec3b>(pi, pj)[] < minPixel[]) {
minPixel[] = pre.at<Vec3b>(pi, pj)[];
}
}
}
}
}
next.at<Vec3b>(i, j) = minPixel;
}
}
pre = next.clone();//更新
}
dst = next.clone();//更新
}
/*
函數: 膨脹操作
參數: src:原圖像
dst:結果圖像
kernel:核矩陣
iterations:操作次數
*/
void dilateFun(Mat &src, Mat &dst, Mat kernel, int iterations) {
Mat_<uchar> kernelMatrix = kernel;
int HALFKERLEN = kernelMatrix.cols / ;//正方形
int KERLEN = kernelMatrix.cols;
Mat pre = src.clone();
Mat next = src.clone();
for (int iterator = ; iterator < iterations; iterator++) {//膨脹次數
for (int i = ; i<pre.rows; i++)
{
for (int j = ; j < pre.cols; j++)
{
//處理每一個像素值的每一個通道
Vec3b maxPixel = pre.at<Vec3b>(i, j);
//周遊核
for (int ki = ; ki < KERLEN; ki++) {
for (int kj = ; kj < KERLEN; kj++) {
if ((int)kernelMatrix.at<uchar>(ki, kj) == ) {//核為1,處理
int pi = ki + i - HALFKERLEN;
int pj = kj + j - HALFKERLEN;
if (pi >= && pi < pre.rows && pj >= && pj < pre.cols)//不超限
{
if (pre.at<Vec3b>(pi, pj)[] > maxPixel[]) {
maxPixel[] = pre.at<Vec3b>(pi, pj)[];
}
if (pre.at<Vec3b>(pi, pj)[] > maxPixel[]) {
maxPixel[] = pre.at<Vec3b>(pi, pj)[];
}
if (pre.at<Vec3b>(pi, pj)[] > maxPixel[]) {
maxPixel[] = pre.at<Vec3b>(pi, pj)[];
}
}
}
}
}
next.at<Vec3b>(i, j) = maxPixel;
}
}
pre = next.clone();
}
dst = next.clone();
}
/*
函數: 開操作
參數: src:原圖像
dst:結果圖像
kernel:核矩陣
iterations:操作次數
*/
void openFun(Mat &src, Mat &dst, Mat kernel, int iterations) {
Mat erode = src.clone();
erodeFun(src, erode, kernel, iterations); //腐蝕
dilateFun(erode, dst, kernel, iterations);//膨脹
}
/*
函數: 閉操作
參數: src:原圖像
dst:結果圖像
kernel:核矩陣
iterations:操作次數
*/
void closeFun(Mat &src, Mat &dst, Mat kernel, int iterations) {
if (iterations == ) { return; }
Mat dilate = src.clone();
dilateFun(src, dilate, kernel, iterations); //腐蝕
erodeFun(dilate, dst, kernel, iterations);//膨脹
}
void subtractFun(Mat src, Mat erode_ouput, Mat &externalGradientImg, int threshold) {
for (int i = ; i < src.rows; i++) {
for (int j = ; j < src.cols; j++) {
/*cout << (int)src.at<Vec3b>(i, j)[0] << " ";
cout << (int)src.at<Vec3b>(i, j)[1] << " ";
cout << (int)src.at<Vec3b>(i, j)[2] << " " << endl;*/
if (src.at<Vec3b>(i, j)[] - erode_ouput.at<Vec3b>(i, j)[] > threshold ||
src.at<Vec3b>(i, j)[] - erode_ouput.at<Vec3b>(i, j)[] > threshold ||
src.at<Vec3b>(i, j)[] - erode_ouput.at<Vec3b>(i, j)[] > threshold) {
externalGradientImg.at<Vec3b>(i, j)[] = ;
externalGradientImg.at<Vec3b>(i, j)[] = ;
externalGradientImg.at<Vec3b>(i, j)[] = ;
}
else {
externalGradientImg.at<Vec3b>(i, j)[] = ;
externalGradientImg.at<Vec3b>(i, j)[] = ;
externalGradientImg.at<Vec3b>(i, j)[] = ;
}
}
}
}
main.cpp
#include "stdafx.h"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
#include "morphology.h"
using namespace cv;
/// 全局變量
Mat src, outLineResult;
int kernel_type = ;//核樣式
int const kernel_type_max = ;
int kernel_size = ;//核尺寸
int const kernel_size_max= ;
int erode_times = ;//腐蝕次數
int const erode_times_max = ;
int removedetails_times = ;//去除細節次數
int const removedetails_times_max = ;
void outLine(int, void*);
int main()
{
src = imread("test.png");
if (!src.data)
{
return -;
}
/// 建立顯示視窗
namedWindow("Erosion Demo", CV_WINDOW_AUTOSIZE);
namedWindow("Dilation Demo", CV_WINDOW_AUTOSIZE);
cvMoveWindow("Dilation Demo", src.cols, );
createTrackbar("核樣式:", "Erosion Demo",
&kernel_type, kernel_type_max,
outLine);
createTrackbar("核尺寸:", "Erosion Demo",
&kernel_size, kernel_size_max,
outLine);
createTrackbar("腐蝕次數:", "Erosion Demo",
&erode_times, erode_times_max,
outLine);
createTrackbar("去噪強度:", "Erosion Demo",
&removedetails_times, removedetails_times_max,
outLine);
/// Default start
outLine(, );
waitKey();
return ;
}
/** @function Erosion */
void outLine(int, void*)
{
int type;
string tpyeStr = "";
string removeDetailType = "";
if (kernel_type == ) { type = MORPH_RECT; tpyeStr = "正方形"; }
else if (kernel_type == ) { type = MORPH_CROSS; tpyeStr = "十字形"; }
else if (kernel_type == ) { type = MORPH_ELLIPSE; tpyeStr = "圓形"; }
//輸出
cout << endl<<"----------------------" << endl;
cout << " 核樣式:"<< tpyeStr;
cout << " 核尺寸:" << kernel_size;
cout << " 輪廓強度:" << erode_times;
cout << " 細節去除樣式:" << removeDetailType;
cout << " 細節去除強度:" << removedetails_times;
Mat element = getKernelMatrix(type, Size(kernel_size, kernel_size));
Mat erode_ouput,closeResult,openResult;
erode_ouput = src.clone();
outLineResult = src.clone();
openResult = src.clone();
closeResult = src.clone();
//噪聲濾除
if (removedetails_times == ) {
closeFun(src, closeResult, element, );
openFun(closeResult, openResult, element, );
}
else if (removedetails_times == ) {
openFun(src, openResult, element, );//去噪
closeFun(openResult, closeResult, element, );
openFun(closeResult, openResult, element, );//去噪
closeFun(openResult, closeResult, element, );
}
// 腐蝕操作
erodeFun(closeResult, erode_ouput, element , erode_times);
//減操作
subtractFun(closeResult, erode_ouput, outLineResult,);
imshow("Erosion Demo", outLineResult);
cout << " 完成";
}
人臉邊緣輪廓提取(下巴、嘴唇)
使用上述方法對人臉進行輪廓提取,調整參數,得到相對最佳的效果,但是會出現下巴和嘴唇提取失敗的現象,是以針對這些邊緣資訊,要自行提取。
- 流程分析:
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) -
嘴唇定位&内外輪廓及中心點确認:
使用人臉特征提取和模式比對方法去定位嘴唇并确認中心點以及内外輪廓。
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇) - 自适應算法确定輪廓點
圖像形态學輪廓處理&人臉邊緣輪廓提取(下巴、嘴唇)
Step1:确定路徑L(i)的起始點start(i)和終止點end(i) 。
Step2:從start(i)向end(i)做步長為1的像素點搜尋,每次搜尋計算像素梯度值,當梯度值大于設定的門檻值時,停止該次搜尋,并儲存該點為輪廓點。
Step3:确定是否完成搜尋:如完成,進行下一步輪廓提取。如果未完成,更新搜尋路徑L(i+1),轉至Step1.
提取結果:
改變搜尋方向,即可對嘴唇進行輪廓點的提取: