实验一

实验内容:

1、利用opencv读取图片

在opencv中读取一张图片使用imread()函数,比如:

Mat src = imread("D:/images/dog.jpg", IMREAD_COLOR)。

imread()一般有两个参数,前一个是图片地址,另外一个是读取图片的格式。

这里采用的IMREAD_COLOR只的读取为默认的BGR的格式,常见的有读取为灰度的格式,就可以使用
IMREAD_GRAYSCALE。当然这个参数也可以是数字,比如“-1”,代表读取默认格式。
当我们读取一张图片后可以用empty()函数,判断读取是否成功。比如:

if (src.empty()){
	cout << "can`t open this ph!" << endl;
	exit(0);
}

读取一张图片后,进行展示需要用到imshow()函数,imshow()一般有两个参数,第一个是窗口名称,第二个参数是一个Mat对象。比如这样:

imshow("input", origin)。

单独使用imshow()给出的窗口的大小和Mat对象的大小有关,且不能调整。这时候就需要设置窗口,要用到namedWindow()函数,这个函数一般也是有两个参数,第一个是窗口名字,第二个是窗口类型,比如

常用的比如WINDOW_FREERATION,这种窗口是可以拖动大小的,WINDOW_AUTOSIZE是默认大小。具体使用如下:

namedWindow("input",WINDOW_FREERATIO);
imshow("input", origin);

通过imshow()展示只能展示三通道或者单通道的图片,有些图片有其他通道,比如png格式的透明通道是不能展示出来的。要看到效果就需要保存下来,这时候会用到imwrite()函数,这个函数也有两个参数第一个是保存的路径以及名称,第二参数是需要保存的Mat对象。举例:

imwrite("D:/images/green_channels.jpg", src_mat[1]);

只有前面几个步骤,运行的时候展示图片会一闪而过,这时候就要用到waitKey(),常用的waitKey(0),waitKey(27),这里的27是ascll码表中的esc。
做完所有的工作后,记得要销毁所有的窗口,这时候就需要destroyAllWindows()。
比如这部分代码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
void show_photo(const Mat& origin) 
{
	namedWindow("input",WINDOW_FREERATIO);
	imshow("input", origin);
}

int main(int argc, char* argv)
{
	Mat src = imread("D:/images/dog.jpg", IMREAD_COLOR);
	if (src.empty()) {
		cout << "can`t open this ph!" << endl;
		exit(0);
	}
	show_photo(src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

2、灰度图像二值化处理

具体内容:设置并调整阀值对图像二值化处理。
实验是调整阀值对图片的进行二值化处理,这种代码实现也是很简单的。虽然要造车轮子,但是我们也可以先了解现在有哪些二值化的方法。一般对灰度图二值化的方法是调用threshold()方法(后面我们实现下THRESH_BINARY这种方式),常见的有以下几种类型:
threshold(gray,dst,127,255,THRESH_BINARY);
threshold(gray, dst, 127, 255,THRESH_BINARY_INV);
threshold(gray, dst, 127, 255,THRESH_OTSU);
threshold(gray, dst, 127, 255, THRESH_TRUNC);
threshold(gray, dst, 127, 255, THRESH_TOZERO);
如果使用最大最小值这种就可采用THRESH_BINARY和THRESH_BINARY_INV,这里设置127是不合理的,可以通过全局阀值来寻找这个thershold, 比如常见的均值 ,OTSU和Triangle 的方法,还有自适应阈值

THRESH_BINARY

这种方式大概就是实验中要我们实验的方式,这种方式,
If(像素值 >threshold):
像素值 = 255;
else:
像素值 = 0;

THRESH_BINARY_INV

这种方式恰好相反,
If(像素值 >threshold):
像素值 = 0;
else:
像素值 = 255;

THRESH_TRUNC

这种方式,又叫截断,即是
If(像素值 >threshold):
像素值 = threshold;
else:
像素值不变;

THRESH_TOZERO

这种方式,大于阀值原值,小于threshold变为0;
If(像素值 >threshold):
像素值不变;
else:
像素值 = 0;

全局阈值

均值法

这里可以统计每个像素点计算均值,但是没必要。我们可以调用mean()来获取均值
Scalar m = mean(src),这里的src是一个单通道的Mat对象, m[0]就是图片的均值,我们可以选择把均值作为阀值进行二值化。

THRESH_OSTU

这种方式,这种方式是通过全局统计像素点,利用直方图,找到合适的threshold,比如说将一个灰度图分为几个灰度级别,然后在每个灰度级别选择一个灰度级别作为阈值,然后计算方差,对每个级别的方差乘以自身在整个像素分布的权重,得到总共的方法。然后换下一个灰度级别作为阀值再计算,最后选择产生方差最小的那个灰度级别作为阀值。然后在使用上面几种二值化的方法。常见调用方式为
threshold(gray, dst, 0, 255, THRESH_BINARY | THRESH_OTSU);

三角阈值方法

寻找最大d对应的h,α和β一般为45°,h与横坐标交点一般就是我们要寻找的阈值,一般还会再偏移0.2,有时候如果直方图的峰值大于127,还会做一个对称的映射,然后再变换回去。

自适应阈值方法

举例调用:adaptiveThreshold(gray, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 11, 3),这种主要用于像素分布不均的图片二值化 ,这种情况下单独设置唯一阈值,有点不合适。 这种方式的方法一般先对原图进行模糊,模糊的方式可以是多种,比如说盒子模糊或者高斯模糊,模糊后的图片我们成为图片D。然后原图S加上一个偏置常量C,我们记为S_C,
然后S_C 减去D,得到差异部分。这里 S_C - D会大于-C, S_C-D = S+C-D ,如果S_C - D>0, 就让他等于255,小于0就让他等于0。这里依赖于模糊的方法,我们可以有自适应均值分割,自适应高斯分割。

实现THRESH_BINARY

我们用代码是实现下THRESH_BINARY这种方式,我设置一个滑动条来动态改变阈值。
实验代码

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

int th = 0;
Mat src;
void my_thresholdTwoValued(int,void*) {

	int height = src.rows;
	int width = src.cols;
	Mat TwoValued = Mat::zeros(src.size(), CV_8UC1);
	for (size_t row = 0; row < height; row++) {
		for (size_t col = 0; col < width; col++) 
		{
			int pv = src.at<uchar>(row, col);
			if (pv >= th) {
				TwoValued.at<uchar>(row,col) = 255;
			}
			else {
				TwoValued.at<uchar>(row, col) = 0;
			}
		}
	}
	namedWindow("二值化后的图像", WINDOW_FREERATIO);
	imshow("二值化后的图像", TwoValued);
}

int main(int argc, char* argv)
{
	src = imread("D:/images/dog.jpg", IMREAD_GRAYSCALE);

	if (src.empty()) {
		exit(0);
	}
	namedWindow("input", WINDOW_FREERATIO);
	imshow("input", src);
	createTrackbar("阀值", "input", &th, 255, my_thresholdTwoValued);
	my_thresholdTwoValued(127,0);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

实验效果:

当阈值拖动为127的时候:

当阈值拖动到107的时候:

当阈值拖动到90的时候:

阈值越小,白色点越多。

当阈值为145的时候:

阈值越高,黑点越多。

3、灰度图像的对数变换

 具体内容:设置并调整r值对图像进行对数变换。对数变换常用的公式为:
                              s = c*log(1 + r)

其中r, s代表处理前后的像素点的值,c为常数。该变换范围较窄的低灰度映射为输出范围较宽的灰度值。这种类型用来拓展暗像素值,压缩更高灰度级别的像素。反对数变得作用与此相反。

实验代码

将输入的灰度图由CV_8UC1成CV_32FC1

src.convertTo(logSrc, CV_32FC1, 1.0);

将像素值都加上1,这样是避免出现log的结果为负

logSrc += Scalar::all(1);

进行对数变换,并且在结果乘以常数c_
log(logSrc, logSrc);
logSrc = logSrc * c_;
转换为CV_8UC1,并变为8位整数
normalize(logSrc,logSrc, 0, 255,NORM_MINMAX);
Mat dst;
logSrc.convertTo(dst, CV_8UC1, 1.0);
convertScaleAbs(dst,dst);

实验效果

这里我将c的值动态变换了一下 ,1到5之内,效果并不明显。
这段的完整代码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
Mat src;
int c_ = 1;
void Logarithmic_transformation(int, void*)
{
	Mat logSrc;
	src.convertTo(logSrc, CV_32FC1, 1.0);
	logSrc += Scalar::all(1);
	log(logSrc, logSrc);
	logSrc = logSrc * c_;
	normalize(logSrc,logSrc, 0, 255,NORM_MINMAX);
	Mat dst;
	logSrc.convertTo(dst, CV_8UC1, 1.0);
	convertScaleAbs(dst,dst);
	namedWindow("对数变换结果", WINDOW_FREERATIO);
	imshow("对数变换结果", dst);

}

int main(int argc, char* argv)
{
	src = imread("D:/images/nightwalk.jpg", IMREAD_GRAYSCALE);
	if (src.empty()) {
		exit(0);
	}
	namedWindow("input", WINDOW_FREERATIO);
	imshow("input", src);
	
	createTrackbar("c值", "input", &c_, 5, Logarithmic_transformation);
	Logarithmic_transformation(1,0);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

4、灰度图像的伽马变换

 具体内容:设置并调整γ对图像进行伽马变换。伽马变换的基本形式为:
                               s = c·r^{γ}

其中c,γ为正常数,当c=γ=1时就是恒等变换,γ>1 与γ<1的效果相反。
常见的应用:
输入监视器的图像经过伽马变换产生外观上接近于原图像的输出。
使用伽马变换增强对比度。
处理图像的“冲淡”现象,进行灰度的压缩,这时候γ>1。

实验代码

这里我做了个灰度压缩的情况。
实验步骤:
1、将图片由CV_8UC1转换成CV_32FC1
src.convertTo(gammaSrc, CV_32FC1, 1.0);
2、设置c值和gamma值,进行伽马变换。这里代码很简单,我没有逐像素点操作,那个很简单。包括按照数组下标访问,还是指针访问,我都做过,我觉得没必要造这个轮子了。

pow(gammaSrc, gamma,gammaSrc);
int c_ = 1;
gammaSrc = gammaSrc * c_;

3、转化为8位整数格式

normalize(gammaSrc, gammaSrc, 0, 255, NORM_MINMAX);
Mat dst;
gammaSrc.convertTo(dst, CV_8UC1, 1.0);
convertScaleAbs(dst, dst);

实验效果
当c=1,γ=1时,就是原图。

当c=1, 伽马=3时,亮的背景部分变暗,人物轮廓变清晰。

当c=1 ,伽马 = 5时,人物突出,背景亮光部分基本变成黑色。

当c=1,伽马=14时:

实验代码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
Mat src;
int gamma = 2;
void gammaTransformation(int, void*) 
{
	Mat gammaSrc;
	src.convertTo(gammaSrc, CV_32FC1, 1.0);
	pow(gammaSrc, gamma,gammaSrc);
	int c_ = 1;
	gammaSrc = gammaSrc * c_;
	normalize(gammaSrc, gammaSrc, 0, 255, NORM_MINMAX);
	Mat dst;
	gammaSrc.convertTo(dst, CV_8UC1, 1.0);
	convertScaleAbs(dst, dst);
	namedWindow("伽马变换结果", WINDOW_FREERATIO);
	imshow("伽马变换结果", dst);

}
int main(int argc, char** argv) 
{
	src = imread("D:/images/gamma.jpg", IMREAD_GRAYSCALE);
	if (src.empty()) {
		exit(0);
	}
	namedWindow("input",WINDOW_FREERATIO);
	imshow("input", src);
	createTrackbar("gamma值", "input", &gamma, 35, gammaTransformation);
	gammaTransformation(2,0);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

5、彩色图像的补色变换

 具体内容:对彩色图像进行补色变换。

代码实现

	for (int i = 0; i < row; ++i) {
		for (int j = 0; j < col; ++j) {
			int b = src.at<Vec3b>(i, j)[0];
			int g = src.at<Vec3b>(i, j)[1];
			int r = src.at<Vec3b>(i, j)[2];
			int maxrgb = max(max(r, g), b);
			int minrgb = min(min(r, g), b);
			dst.at<Vec3b>(i, j)[0] = maxrgb + minrgb - b;
			dst.at<Vec3b>(i, j)[1] = maxrgb + minrgb - g;
			dst.at<Vec3b>(i, j)[2] = maxrgb + minrgb - r;
		}
	}

对于一个RGB图像像素点来说,就是找到当前三个通道上最大最小值,然后将最大最小值相加,再减去原像素点。
对于RGB的补色有点像转换成CMY色彩空间。

实验结果

对于其他的色彩空间的补色变换。就是对该像素点在色环上找到180度对应的值,色环如下图:

这个我看了下其他博客实现的代码:

void Complementary_Color(C3 *src,C3 *dst,int width,int height,int color_space_type){
    switch (color_space_type) {
        case COLOR_SPACE_RGB:
            RGB2CMY(src, dst, width, height);
            break;
        case COLOR_SPACE_CMY:
            CMY2RGB(src, dst, width, height);
            break;
        case COLOR_SPACE_HSI:{
            for(int i=0;i<width*height;i++){
                double h=src[i].c1;
                if(h>=M_PI)
                    dst[i].c1=h-M_PI;
                else
                    dst[i].c1=M_PI+h;
                dst[i].c2=src[i].c2;
                dst[i].c3=255.-src[i].c3;
            }
            break;
        }
        case COLOR_SPACE_HSV:{
            for(int i=0;i<width*height;i++){
                double h=src[i].c1;
                if(h>=180.0)
                    dst[i].c1=h-180.0;
                else
                    dst[i].c1=180.0+h;
                dst[i].c2=src[i].c2;
                dst[i].c3=1.0-src[i].c3;
            }
            break;
        }
        default:
            break;
    }
}

实验遇到问题

1、 对于彩色图片的补色操作,对于不同的色彩空间有不同的操作,在实验中我只选取了RGB色彩空间的补色。其他色彩空间的补色,我参考了别人的博客:https://www.cnblogs.com/bhlsheji/p/5215171.html

2、在二值化实验中,我实现的是基于阀值T的情况,即是大于T为255,小于T为0.其他的几种情况原理我都列举出来了,实现不难。全局阈值的情况,我介绍了均值法,OSTU以及三角法的简单原理。自适应分割的两种方法,我简单的介绍了原理。

3、在对数变换和伽马变换中,我没有逐像素点去计算,其实过程也很简单。只要会像素点访问方式就明白,一种是基于数组下标的方式,一种是基于指针的操作。我将这两种方式放在我的git上https://github.com/cyssmile/openCV_learning_notes/blob/master/opencv_test/opencv_004_point/main.cpp

posted @ 2020-05-27 23:27  cyssmile  阅读(302)  评论(0编辑  收藏  举报