一、人脸皮肤诊断方法
近年来,随着计算机技术和人工智能的不断发展,中医领域开始逐渐探索利用这些先进技术来辅助面诊和诊断。在皮肤望诊方面,也出现了一些现代研究,尝试通过图像分析技术和人工智能算法来客观化地获取皮肤相关的色形参数,从而辅助中医面诊。
一些研究将计算机视觉和图像处理技术应用于皮肤望诊,旨在提取皮肤颜色、纹理、斑点等特征,然后通过模式识别算法来进行分析和诊断。这些研究通常需要大量的医学图像数据作为基础,使用机器学习和深度学习技术,如卷积神经网络(CNN),来训练模型以识别不同的皮肤状况和问题。
在中医面诊中,色形参数是评估患者身体状况的重要指标之一。这些参数通常包括皮肤颜色的变化、皮肤纹理的变化、斑点的出现等。通过对这些参数进行量化分析,可以帮助中医医生更客观地了解患者的身体状况,并作出相应的诊断和治疗建议。
然而,需要注意的是,虽然现代技术可以在一定程度上辅助中医面诊,但中医诊断是一门复杂的艺术和科学,它涉及到诸多因素,包括患者的整体状况、舌诊脉诊等。因此,尽管现代技术可以提供一些有用的信息,但在中医面诊中仍需要结合传统的临床经验和知识进行综合判断。
1.颜色
现代色度学研究认为,颜色的基本要素包括色调、饱和度和亮度。在医学领域,特别是在中医色诊客观化研究中,选择适当的颜色模型以准确地描述望诊五色特征是一个具有挑战性的问题。虽然多种颜色模型可供选择,如RGB、YCrCb、Lab、YUV、HLS、Ohta和Hue模型等,但目前还没有统一的标准确定哪种模型最适合中医色诊的研究,能够更准确地表达中医的望诊五色特征。
尽管各种颜色模型都具有优势,但确切地确定哪种模型在中医色诊客观化研究中更适用仍然是一个待解决的问题。RGB模型在应用中较为广泛,但它可能并不完全符合人类视觉感知的特性。
Lab模型是一个更符合人类视觉感知的颜色空间,可能在中医色诊的客观化研究中具有优势,但在实际应用中仍需深入研究。
另一方面,肤色在YCbCr空间可能具有一定的聚类特性和稳定性,这为中医望诊提供了一种可能性,但也需要进一步的实证研究来验证其在中医面色望诊中的可行性和准确性。
2.纹理和皱纹
人体皮肤表面的纹理和皱纹是生理和年龄变化的结果。皮肤纹理是由微小的皮丘和皮沟组成的,而随着时间的推移和外部环境的影响,皮肤纹理可能会发生变化,形成皱纹。这些变化可能是由于皮肤的自然衰老、过度暴晒太阳、重复的肌肉运动等因素引起的。在现代研究中,关于皮肤纹理和皱纹的特征提取已经逐渐完善。
在面部纹理特征的提取方面,研究者已经提出了多种方法。一些方法包括使用灰度信息,如基于Gabor滤波的方法,用于人脸和掌纹的识别。此外,灰度共生矩阵(GLCM)和局部二值模式(LBP)等统计学方法也被用来提取纹理特征。这些方法可以帮助描绘纹理的细节特征,对皮肤纹理进行精细评价。
特别是,采用灰度共生矩阵法提取皮肤纹理特征被认为是合理有效的方法。灰度共生矩阵通过分析像素之间的灰度关系来捕捉纹理信息,这种方法能够较好地刻画出皮肤纹理的细节特征。
在研究皱纹时,常见的皱纹部位包括额纹、眼角纹、川字纹、法令纹、嘴角纹等。研究者们采用不同的方法来评价和提取皱纹特征,包括主观评分、图像处理软件(如Photoshop)的评价以及一些特定设备的评价,如Visioscan。这些方法在不同的环境下,从不同的角度提取皱纹特征,可以帮助更好地理解和评估皮肤纹理的变化。
总体而言,现代研究通过多种方法来研究皮肤纹理和皱纹的特征提取,这些方法在皮肤疾病诊断、美容医学等领域具有潜在的应用前景。但要注意,纹理和皱纹的变化是复杂的生理过程,综合考虑多种特征和方法可能有助于更准确地评价皮肤的状态和健康状况。
3.毛孔
根据皮肤学研究,毛孔是皮肤表面的微小凹孔,其尺寸范围通常在50到500微米之间。对毛孔的评价方法包括等级评分法、标准照片评分法、皮肤镜检测、Visia皮肤检测仪以及算法识别等多种方式。
针对毛孔的研究,研究者们提出了不同的方法来评估和描述毛孔的特征。其中,一些研究基于数字化手段,提出了一些精细的参数来描述毛孔的情况。例如,研究者提出了“皮肤毛孔整体粗糙度”这一参数,通过数字化的方式更准确地评估面部毛孔的粗糙程度。另外,一些研究利用改进的算法来分割毛孔,从而获得毛孔的色调、形状和尺寸等特征。这些方法都旨在通过数字化分析来获得关于毛孔的更精确的信息。
此外,皮肤镜检测也被应用于毛孔的研究。皮肤镜可以识别计算毛孔的平均面积,并通过比较毛孔内部颜色与周围区域的颜色差异来表示毛孔的特征。这些方法都帮助了毛孔特征的客观化分析。
对于色斑的研究,研究者们也提出了多种方法。颜色直方图中的HSV空间模型被应用于描述色斑的颜色信息。同时,通过摄像机获取图像并对色斑进行量化分析,可以得出色斑的几何信息,如面积、周长、最大直径、几何形状和对称性等。
总的来说,图像分析法在皮肤学研究中具有许多优点,如客观性、操作简便、重复性好等。通过这些方法,可以定量地获取皮肤特征的信息,用于医学诊断、皮肤评价以及化妆品和皮肤病治疗前后的比较。不过,这些方法的应用还需要进一步的研究和验证,以确保其准确性和可靠性。
二、人脸皮肤区域获取
1.人脸皮肤分割
在做皮肤检测前提前条件是先把人脸分割出来,人脸皮肤分割是指将人脸图像中的皮肤部分从其他背景或物体中分离出来的过程。常用的方向有以下几种:
- 基于颜色阈值的方法: 人脸皮肤通常具有特定的颜色范围,比如在RGB颜色空间中,皮肤可能落在一定的红色、绿色和蓝色通道值范围内。通过设置适当的颜色阈值,可以将皮肤像素从其他像素中分离出来。然而,这种方法容易受到光照变化和肤色多样性的影响,导致分割效果不稳定。
- 基于机器学习的方法: 使用机器学习算法,如支持向量机(SVM)、随机森林、卷积神经网络(CNN)等,可以训练一个分类器,将皮肤像素与非皮肤像素分开。这需要大量的标注数据进行训练,但结果通常更准确。
- 基于深度学习的方法: 使用深度学习技术,特别是卷积神经网络(CNN),可以更精确地进行皮肤分割。可以设计一个CNN架构,输入人脸图像,输出一个相应大小的二值分割掩码,其中皮肤区域被标记为1,非皮肤区域被标记为0。
- 基于图像分割算法的方法: 图像分割算法,如基于区域的分割(如区域增长、分水岭算法)、基于边缘的分割(如Canny边缘检测)等,也可以应用于人脸皮肤分割。这些方法通过分析像素之间的相似性或差异性来确定皮肤区域。
这里使用的基于深度学习的face-parsing 。训练出模型之后,转成onnx,然后使用onnxruntime进行推理:
#include "face_parsing_bisenet.h"
#include "../core/ort_utils.h"
using ortcv::FaceParsingBiSeNet;
Ort::Value FaceParsingBiSeNet::transform(const cv::Mat &mat)
{
cv::Mat canvas;
cv::resize(mat, canvas, cv::Size(input_node_dims.at(3), input_node_dims.at(2)));
cv::cvtColor(canvas, canvas, cv::COLOR_BGR2RGB);
// e.g (1,3,512,512)
ortcv::utils::transform::normalize_inplace(canvas, mean_vals, scale_vals);
return ortcv::utils::transform::create_tensor(
canvas, input_node_dims, memory_info_handler,
input_values_handler, ortcv::utils::transform::CHW); // deepcopy inside
}
void FaceParsingBiSeNet::detect(const cv::Mat &mat, types::FaceParsingContent &content,
std::vector<cv::Mat>& cv_features,bool minimum_post_process)
{
if (mat.empty()) return;
// 1. make input tensor
Ort::Value input_tensor = this->transform(mat);
// 2. inference
auto output_tensors = ort_session->Run(
Ort::RunOptions{
nullptr}, input_node_names.data(),
&input_tensor, 1, output_node_names.data(), num_outputs
);
// 3. generate mask
this->generate_mask(output_tensors, mat, content,cv_features, minimum_post_process);
}
static inline uchar argmax(float *mutable_ptr, const unsigned int &step)
{
std::vector<float> logits(19, 0.f);
for (unsigned int i = 0; i < 19; ++i)
logits[i] = *(mutable_ptr + i * step);
uchar label = 0;
float max_logit = logits[0];
for (unsigned int i = 1; i < 19; ++i)
{
if (logits[i] > max_logit)
{
max_logit = logits[i];
label = (uchar) i;
}
}
return label;
}
static const uchar part_colors[20][3] = {
{
0, 0, 0},
{
0, 0, 255},//脸
{
255, 170, 0},//右眉毛
{
255, 0, 85},//左眉毛
{
0, 0, 0},
{
0, 0, 0},
{
0, 0, 0},
{
0, 0, 0},
{
0, 0, 0},//耳朵
{
0, 0, 0},
{
0, 170, 255},//鼻子
{
0, 0, 0},
{
0, 125, 255},//上嘴唇
{
0, 255, 0},//下嘴唇
{
0, 0, 0},
{
0, 0, 0},
{
0, 0, 0},
{
0, 0, 0},//头发
{
0, 0, 0},
{
0, 0, 0}
};
void FaceParsingBiSeNet::generate_mask(std::vector<Ort::Value> &output_tensors, const cv::Mat &mat,
types::FaceParsingContent &content, std::vector<cv::Mat>& cv_features,
bool minimum_post_process)
{
cv_features.clear();
Ort::Value &output = output_tensors.at(0); // (1,19,h,w)
const unsigned int h = mat.rows;
const unsigned int w = mat.cols;
auto output_dims = output.GetTypeInfo().GetTensorTypeAndShapeInfo().GetShape();
const unsigned int out_h = output_dims.at(2);
const unsigned int out_w = output_dims.at(3);
const unsigned int channel_step = out_h * out_w;
float *output_ptr = output.GetTensorMutableData<float>();
std::vector<uchar> elements(channel_step, 0); // allocate
for (unsigned int i = 0; i < channel_step; ++i)
{
elements[i] = argmax(output_ptr + i, channel_step);
}
cv::Mat label(out_h, out_w, CV_8UC1, elements.data());
cv::Mat cv_EB(out_h, out_w, CV_8UC1, cv::Scalar(0));
cv::Mat cv_face = cv_EB.clone();
cv::Mat cv_nose = cv_EB.clone();
cv::Mat cv_ulip = cv_EB.clone();
cv::Mat cv_dlip = cv_EB.clone();
cv::Mat cv_all = cv_EB.clone();
if (!minimum_post_process)
{
const uchar *label_ptr = label.data;
cv::Mat color_mat(out_h, out_w, CV_8UC3, cv::Scalar(0, 0, 0));
for (unsigned int i = 0; i < cv_EB.rows; ++i)
{
cv::Vec3b* p = color_mat.ptr<cv::Vec3b>(i);
uchar* EP = cv_EB.ptr<uchar>(i);
uchar* face = cv_face.ptr<uchar>(i);
uchar* nose = cv_nose.ptr<uchar>(i);
uchar* ulip = cv_ulip.ptr<uchar>(i);
uchar* dlip = cv_dlip.ptr<uchar>(i);
uchar* all = cv_all.ptr<uchar>(i);
for (unsigned int j = 0; j < cv_EB.cols; ++j)
{
if (label_ptr[i * out_w + j] == 0) continue;
p[j][0] = part_colors[label_ptr[i * out_w + j]][0];
p[j][1] = part_colors[label_ptr[i * out_w + j]][1];
p[j][2] = part_colors[label_ptr[i * out_w + j]][2];
switch (label_ptr[i * out_w + j])
{
case 1://脸
all[j] = 255;
face[j] = 255;
break;
case 2://眉毛
all[j] = 255;
EP[j] = 255;
break;
case 3:
all[j] = 255;
EP[j] = 255;
break;
case 10://鼻子
all[j] = 255;
nose[j] =