实现了二值图像的Thin,Thicken和提取骨架的操作。
#include "opencv2/opencv.hpp"
#define HIT 1
#define MISS 0
using namespace cv;
using namespace std;
const int dir[9][2] = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,0},{0,1},{1,-1},{1,0},{1,1}};
//定义了skeleton和convex hull操作用到的各八个structuring element
//structuring element可根据不同需要改变,这里只是一种
#define ELE_STR_SIZE 3
#define SKELETON_STR_ELE_NUM 8
const unsigned char skeleton_str_ele[SKELETON_STR_ELE_NUM][ELE_STR_SIZE][ELE_STR_SIZE] = {
{
{0,0,0},
{2,1,2},
{1,1,1}
},
{
{2,0,0},
{1,1,0},
{2,1,2}
},
{
{1,2,0},
{1,1,0},
{1,2,0}
},
{
{2,1,2},
{1,1,0},
{2,0,0}
},
{
{1,1,1},
{2,1,2},
{0,0,0}
},
{
{2,1,2},
{0,1,1},
{0,0,2}
},
{
{0,2,1},
{0,1,1},
{0,2,1}
},
{
{0,0,2},
{0,1,1},
{2,1,2}
}
};
#define CONVEX_HULL_STR_ELE_NUM 8
const unsigned char convex_hull_str_ele[CONVEX_HULL_STR_ELE_NUM][ELE_STR_SIZE][ELE_STR_SIZE] = {
{
{1,1,2},
{1,0,2},
{1,2,0}
},
{
{2,1,1},
{2,0,1},
{0,2,1}
},
{
{1,1,1},
{2,0,1},
{0,2,2}
},
{
{0,2,2},
{2,0,1},
{1,1,1}
},
{
{0,2,1},
{2,0,1},
{2,1,1}
},
{
{1,2,0},
{1,0,2},
{1,1,2}
},
{
{2,2,0},
{1,0,2},
{1,1,1}
},
{
{1,1,1},
{1,0,2},
{2,2,0}
}
};
unsigned char hit_and_miss(unsigned char src[][ELE_STR_SIZE],const unsigned char str_ele[][ELE_STR_SIZE]){
//对一块区域的hit_and_miss判定
for (int i = 0;i < ELE_STR_SIZE;i++){
for (int j = 0;j < ELE_STR_SIZE;j++){
if (str_ele[i][j] == 0 || str_ele[i][j] == 1){
if (str_ele[i][j] != src[i][j]) return MISS;
}
}
}
return HIT;
}
void hit_and_miss(IplImage *src,IplImage **dst,const unsigned char str_ele[][ELE_STR_SIZE])
{
//对一个二值图像的hit_and_miss操作
unsigned char matrix[ELE_STR_SIZE][ELE_STR_SIZE];
int i1,j1,nx,ny;
CvSize size = cvGetSize(src);
if ((*dst) != NULL) cvReleaseImage(dst);
(*dst) = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
cvZero(*dst);
for (int i = 0;i<size.height;i++){
for (int j = 0;j<size.width;j++){
i1 = 0;
j1 = 0;
for (int k = 0;k < 9;k++){
//把自身及相邻共九个格子放入一个矩阵中
nx = i + dir[k][0];
ny = j + dir[k][1];
if (nx<0 || nx>=size.height || ny<0 || ny>=size.width) break;//超出边界,无需判断
matrix[i1][j1] = CV_IMAGE_ELEM(src,uchar,nx,ny);
++j1;
if (j1 == ELE_STR_SIZE){
++i1;
j1 = 0;
}
}
if (i1 != ELE_STR_SIZE){
CV_IMAGE_ELEM(*dst,uchar,i,j) = 0;
}
else{
CV_IMAGE_ELEM(*dst,uchar,i,j) = hit_and_miss(matrix,str_ele);
}
}
}
}
void biImageSubstract(IplImage *src1,IplImage *src2,IplImage **dst){
//二值图像减法操作
CvSize size1 = cvGetSize(src1);
CvSize size2 = cvGetSize(src2);
if ((*dst) != NULL) cvReleaseImage(dst);
if ((size1.height != size2.height) || (size1.width != size2.width)){
(*dst) = NULL;//大小不同则直接退出
return;
}
//产生一张用于保存结果的空图
(*dst) = cvCreateImage(cvGetSize(src1), IPL_DEPTH_8U, 1);
cvZero(*dst);
//按减法即为交补集的定义计算
for (int i = 0;i<size1.height;i++){
for (int j = 0;j<size1.width;j++){
CV_IMAGE_ELEM(*dst,uchar,i,j) = CV_IMAGE_ELEM(src1,uchar,i,j) & (CV_IMAGE_ELEM(src2,uchar,i,j)^1);
}
}
}
void biImageUnion(IplImage *src1,IplImage *src2,IplImage **dst){
//二值图像的并操作
CvSize size1 = cvGetSize(src1);
CvSize size2 = cvGetSize(src2);
if ((*dst) != NULL) cvReleaseImage(dst);
if ((size1.height != size2.height) || (size1.width != size2.width)){
(*dst) = NULL;//大小不同则直接退出
return;
}
//产生一张用于保存结果的空图
(*dst) = cvCreateImage(cvGetSize(src1), IPL_DEPTH_8U, 1);
cvZero(*dst);
for (int i = 0;i<size1.height;i++){
for (int j = 0;j<size1.width;j++){
CV_IMAGE_ELEM(*dst,uchar,i,j) = CV_IMAGE_ELEM(src1,uchar,i,j) | CV_IMAGE_ELEM(src2,uchar,i,j);
}
}
}
bool equals(IplImage *src1,IplImage *src2){
//判定两个二值图像是否相同
CvSize size1 = cvGetSize(src1);
CvSize size2 = cvGetSize(src2);
if ((size1.height != size2.height) || (size1.width != size2.width)) return false; //大小不同则直接退出
for (int i = 0;i<size1.height;i++){
for (int j = 0;j<size1.width;j++){
if (CV_IMAGE_ELEM(src1,uchar,i,j)!=CV_IMAGE_ELEM(src2,uchar,i,j)) return false; //判定到不同则直接退出
}
}
return true;
}
void thicken(IplImage *src,const unsigned char str_ele[][ELE_STR_SIZE],IplImage**dst){
//二值图像thicken操作
IplImage *hitnmiss = NULL;
//产生一张用于保存结果的空图
if ((*dst) != NULL) cvReleaseImage(dst);
(*dst) = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
cvZero(*dst);
//按定义执行thicken操作
hit_and_miss(src,&hitnmiss,str_ele);
biImageUnion(src,hitnmiss,dst);
cvReleaseImage(&hitnmiss);
}
void thin(IplImage *src,const unsigned char str_ele[][ELE_STR_SIZE],IplImage**dst){
//二值图像thin操作
IplImage *hitnmiss = NULL;
//产生一张用于保存结果的空图
if ((*dst) != NULL) cvReleaseImage(dst);
(*dst) = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
cvZero(*dst);
//按定义执行thin操作
hit_and_miss(src,&hitnmiss,str_ele);
biImageSubstract(src,hitnmiss,dst);
cvReleaseImage(&hitnmiss);
}
void skeleton(IplImage *src,IplImage **dst){
//二值图像提取骨架操作
bool flag = true;
IplImage *cp_src = NULL;
IplImage *tmp = NULL;
//拷贝原图
cp_src = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
cvCopy(src,cp_src,NULL);
//用给出的八个structuring element对原图不断做thin操作直至图片没有变化
while (flag){
flag = false;
for (int i = 0;i < SKELETON_STR_ELE_NUM;i++){
thin(cp_src,skeleton_str_ele[i],&tmp);
if (!flag && !equals(cp_src,tmp)) flag = true;
cvReleaseImage(&cp_src);
cp_src = tmp;
tmp = NULL;
}
}
if ((*dst) != NULL) cvReleaseImage(dst);
(*dst) = cp_src;
}
void getBinaryImage(IplImage *src,IplImage **dst){
IplImage *grayImage = NULL;
// 转为灰度图
grayImage = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
cvCvtColor(src, grayImage, CV_BGR2GRAY);
//创建二值图
if ((*dst) != NULL) cvReleaseImage(dst);
(*dst) = cvCreateImage(cvGetSize(grayImage), IPL_DEPTH_8U, 1);
cvZero(*dst);//清零
cvThreshold(grayImage,*dst,128,1,CV_THRESH_BINARY_INV);
cvReleaseImage(&grayImage);
}
void reverseBinaryImage(IplImage *src){
//将一个二值图像反转
CvSize size = cvGetSize(src);
for (int i = 0;i < size.height;i++){
for (int j = 0;j < size.width;j++){
CV_IMAGE_ELEM(src,uchar,i,j) ^= 1;
}
}
}
void job1(){
//提取土豆骨架
//以二值图片src为输入,以提取到的skeleton以二值图片输出到dst中。
//做法是在每次迭代中不断用八个Structuring Element去对原图做Thin操作,
//直到某次迭代中图片不再有变化。
IplImage *srcImage = cvLoadImage("potota.jpg", CV_LOAD_IMAGE_UNCHANGED);// 从文件中加载原图
IplImage *binaryImage = NULL;
IplImage *skeletonImage = NULL;
if(srcImage == NULL)
{//如果读入图像失败
fprintf(stderr, "Can not load image\n");
return;
}
getBinaryImage(srcImage,&binaryImage);
reverseBinaryImage(binaryImage);//因为原土豆图黑底白色,将前后反转
skeleton(binaryImage,&skeletonImage);//提取骨架
cvReleaseImage(&binaryImage);
//把提取到的skeleton用绿色标注在原图上
CvSize size =cvGetSize(skeletonImage);
for (int i = 0;i < size.height;i++){
for (int j = 0;j < size.width;j++){
if (CV_IMAGE_ELEM(skeletonImage,uchar,i,j) == 1){
CvScalar s = cvGet2D(srcImage,i,j);
s = CV_RGB(0,255,0);
cvSet2D(srcImage,i,j,s);
}
}
}
cvReleaseImage(&skeletonImage);
cvShowImage("Skeleton",srcImage);
cvWaitKey(0);
cvDestroyWindow("Skeleton");
cvReleaseImage(&srcImage);
}
void job2(){
//做一次thin操作
//以二值图片src和结构元素str_ele作为输入,把操作结果以二值图片输出到dst中。
//做法是先将原图和结果元素做一次Hit-and-Miss操作,再用原图减去操作结果。
unsigned char str_ele[3][3] = {
{2,1,2},
{1,1,1},
{2,1,2}
};//操作用到的structuring element
IplImage *srcImage = cvLoadImage("potota.jpg", CV_LOAD_IMAGE_UNCHANGED);// 从文件中加载原图
IplImage *binaryImage = NULL;
IplImage *thinImage = NULL;
if(srcImage == NULL)
{//如果读入图像失败
fprintf(stderr, "Can not load image\n");
return;
}
getBinaryImage(srcImage,&binaryImage);
reverseBinaryImage(binaryImage);//因为原土豆图黑底白色,将前后反转
thin(binaryImage,str_ele,&thinImage);//thin操作
cvReleaseImage(&binaryImage);
//把thin操作的结果用绿色标注在原图上
CvSize size =cvGetSize(thinImage);
for (int i = 0;i < size.height;i++){
for (int j = 0;j < size.width;j++){
if (CV_IMAGE_ELEM(thinImage,uchar,i,j) == 1){
CvScalar s = cvGet2D(srcImage,i,j);
s = CV_RGB(0,255,0);
cvSet2D(srcImage,i,j,s);
}
}
}
cvReleaseImage(&thinImage);
cvShowImage("Thin",srcImage);
cvWaitKey(0);
cvDestroyWindow("Thin");
cvReleaseImage(&srcImage);
}
void job3(){
//做一次thicken操作
//以二值图片src和结构元素str_ele作为输入,把操作结果以二值图片输出到dst中。
//做法是先将原图和结果元素做一次Hit-and-Miss操作,再用原图并上操作结果。
unsigned char str_ele[3][3] = {
{2,1,2},
{1,1,1},
{2,1,2}
};//操作用到的structuring element
IplImage *srcImage = cvLoadImage("potota.jpg", CV_LOAD_IMAGE_UNCHANGED);// 从文件中加载原图
IplImage *binaryImage = NULL;
IplImage *thickenImage = NULL;
if(srcImage == NULL)
{//如果读入图像失败
fprintf(stderr, "Can not load image\n");
return;
}
getBinaryImage(srcImage,&binaryImage);
reverseBinaryImage(binaryImage);//因为原土豆图黑底白色,将前后反转
thicken(binaryImage,str_ele,&thickenImage);//thicken操作
cvReleaseImage(&binaryImage);
//把thicken操作的结果用绿色标注在原图上
CvSize size =cvGetSize(thickenImage);
for (int i = 0;i < size.height;i++){
for (int j = 0;j < size.width;j++){
if (CV_IMAGE_ELEM(thickenImage,uchar,i,j) == 1){
CvScalar s = cvGet2D(srcImage,i,j);
s = CV_RGB(0,255,0);
cvSet2D(srcImage,i,j,s);
}
}
}
cvReleaseImage(&thickenImage);
cvShowImage("Thicken",srcImage);
cvWaitKey(0);
cvDestroyWindow("Thicken");
cvReleaseImage(&srcImage);
}
int main()
{
job1();
job2();
job3();
return 0;
}
原图:
Skeleton效果:
Thin效果:
Thicken效果:
Thin和Thick使用的element::
提取skeleton使用的elemeng(每四个方向只画一个):
相关操作的概念见:http://homepages.inf.ed.ac.uk/rbf/HIPR2/morops.htm