图像运算、增强

目录

一,图像增强

二,空域方法——点处理

1,反转变换

2,对数变换

3,幂次变换

4,对比度拉伸

5,灰度级切片

6,位平面切片

三,图像运算

1,一元运算

(1)非

(2)数乘

2,二元逻辑运算

(1)与

(2)或

(3)异或

3,算术运算

(1)加

(2)减

(3)乘

(4)除

4,综合运算

(1)加权平均

四,直方图均衡、匹配

1,直方图表示方法

2,直方图均衡

(1)直方图均衡的条件

(2)直方图的最大均衡

(3)库函数

3,直方图匹配

4,局部直方图处理

五,二值化

六,空域方法——模板处理

七,频域方法


一,图像增强

图像增强的处理方法,包括空域方法、频域方法。

而空域方法包括点处理(变换)、模板处理(滤波)

二,空域方法——点处理

点运算都可以表示成,用一个一元函数,把所有像素点的灰度值进行映射,得到的新的图像。

1,反转变换

s = L-1 -r

[0,L-1]为图像的灰度级。作用:黑的变白,白的变黑

代码:

import cv2

def maxg(image):
    ans = 0
    for i in range(image.shape[0]):
        for j in range(image.shape[1]):
            ans = max(ans,image[i,j])
    return ans


image = cv2.imread("D:/im.jpg",0)
cv2.imshow("old",image)
L = maxg(image)+1
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        image[i,j]=L-1-image[i,j]
cv2.imshow("new",image)
cv2.waitKey(0)

2,对数变换

有时原图的动态范围太大,超出某些显示设备的允许 动态范围,如直接使用原图,则一部分细节可能丢失。解决办法是对原图进行灰度压缩,如对数变换。

s = c log(1+r) ,其中c是常数

3,幂次变换

4,对比度拉伸

提高图像处理时灰度级的动态范围。

5,灰度级切片

利用切片扣出我们关心的部分,常见的两种切片:

   

6,位平面切片

假设图像中每个像素的灰度级是256,这可以用8位来表 示,假设图像是由8个1位平面组成,范围从位平面0到位 平面7。其中,位平面0包含图像中像素的最低位,位平面 7包含像素的最高位。

较高位(如前4位)包含大多数视觉重要数据,较低位(如后4位)对图像中的微小细节有作用。

三,图像运算

1,一元运算

(1)非

g(x,y) = 255 - f(x,y)

手动实现:

import cv2

image = cv2.imread("D:/im.jpg")
cv2.imshow("old",image)
L = 256
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        image[i,j]=L-1-image[i,j]
cv2.imshow("new",image)
cv2.waitKey(0)

也可以直接调用库函数:

cv2.bitwise_not(image,image2)

第一张图,熊猫,灰度级是0-255,非运算和反转运算相同。

第二张图,素描,灰度级是0-180,非运算出来的底色还是灰色,而反转运算出来的底色是纯黑色。

(2)数乘

手动实现:

for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        image[i, j] *= 0.55

背景值从250变成 250*0.55=137

for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        image[i, j] *= 10

背景值从250变成 250*10%256=196

调用库函数:

image2 = image*10

如果是乘以小数,会把图像转化成CV_64F的图像,所以需要转换:

image2 = cv2.cvtColor(numpy.uint8(image*0.8),cv2.CV_8UC1)

2,二元逻辑运算

(1)与

import cv2

image = cv2.imread("D:/im.jpg",0)
image2 = cv2.imread("D:/im2.jpg",0)
cv2.imshow("img",image)
cv2.imshow("img2",image2)
image3=image2
cv2.bitwise_and(image,image2,image3)
cv2.imshow("img3",image3)
cv2.waitKey(0)

(2)或

cv2.bitwise_or(image,image2,image3)

(3)异或

cv2.bitwise_xor(image,image2,image3)

3,算术运算

(1)加

python:

手动实现:

for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        image2[i,j] += image[i,j]

或者直接用库函数:

image4 = image+image2

加法的结果如果超过上限值(如255),那么结果就mod 256,相当于和c语言中整数的溢出规则相同,减法同理,下文不再赘述。

经过统计发现,img和img2的背景白色值是250,img的背景白色是244

C++中是如果加完超过最大值255,那就结果就是最大值:

	Mat img(2, 2, CV_8U);
	img.at<char>(0, 0) = 1;
	img.at<char>(0, 1) = 2;
	img.at<char>(1, 0) = 3;
	img.at<char>(1, 1) = 130;
	Mat img2(2, 2, CV_8U);
	img2.at<char>(0, 0) = 5;
	img2.at<char>(0, 1) = 6;
	img2.at<char>(1, 0) = 7;
	img2.at<char>(1, 1) = 130;
	Mat img3 = img + img2;
	cout << int(img3.at<char>(0, 0)) << " " << int(img3.at<char>(0, 1)) << endl;
	cout << int(img3.at<char>(1, 0)) << " " << int(img3.at<char>(1, 1)) << endl;

输出:

6 8
10 -1

-1即最大值255

(2)减

python:

image3 = image2-image

这个结果感觉和想象中的不太一样?

C++中是如果减完小于最小值0,那么结果就是0:

	Mat img(2, 2, CV_8U);
	img.at<char>(0, 0) = 1;
	img.at<char>(0, 1) = 2;
	img.at<char>(1, 0) = 3;
	img.at<char>(1, 1) = 130;
	Mat img2(2, 2, CV_8U);
	img2.at<char>(0, 0) = 5;
	img2.at<char>(0, 1) = 6;
	img2.at<char>(1, 0) = 7;
	img2.at<char>(1, 1) = 8;
	Mat img3 = img2 - img;
	cout << int(img3.at<char>(0, 0)) << " " << int(img3.at<char>(0, 1)) << endl;
	cout << int(img3.at<char>(1, 0)) << " " << int(img3.at<char>(1, 1)) << endl;

输出:

4 4
4 0

(3)乘

python:

image3 = image*image2

C++的三种乘法:

矩阵乘法:

不允许CV_8U的图像矩阵相乘,32F可以:

	Mat img(2, 2, CV_32F);
	img.at<float>(0, 0) = 1;
	img.at<float>(0, 1) = 2;
	img.at<float>(1, 0) = 3;
	img.at<float>(1, 1) = 4;
	Mat img2(2, 2, CV_32F);
	img2.at<float>(0, 0) = 5;
	img2.at<float>(0, 1) = 6;
	img2.at<float>(1, 0) = 7;
	img2.at<float>(1, 1) = 8;
	Mat img3 = img * img2;
	cout << int(img3.at<float>(0, 0)) << " " << int(img3.at<float>(0, 1)) << endl;
	cout << int(img3.at<float>(1, 0)) << " " << int(img3.at<float>(1, 1)) << endl;

输出:

19 22
43 50

PS:如果A*B中A的列数和B的行数不一致,则抛出异常。

向量内积:

	Mat img(2, 2, CV_32F);
	img.at<float>(0, 0) = 1;
	img.at<float>(0, 1) = 2;
	img.at<float>(1, 0) = 3;
	img.at<float>(1, 1) = 4;
	Mat img2(2, 2, CV_32F);
	img2.at<float>(0, 0) = 5;
	img2.at<float>(0, 1) = 6;
	img2.at<float>(1, 0) = 7;
	img2.at<float>(1, 1) = 8;
	double x = img.dot(img2);
	cout << x;

输出70

点乘:

	Mat img(2, 2, CV_32F);
	img.at<float>(0, 0) = 1;
	img.at<float>(0, 1) = 2;
	img.at<float>(1, 0) = 3;
	img.at<float>(1, 1) = 4;
	Mat img2(2, 2, CV_32F);
	img2.at<float>(0, 0) = 5;
	img2.at<float>(0, 1) = 6;
	img2.at<float>(1, 0) = 7;
	img2.at<float>(1, 1) = 8;
	Mat img3 = img.mul(img2);
	cout << int(img3.at<float>(0, 0)) << " " << int(img3.at<float>(0, 1)) << endl;
	cout << int(img3.at<float>(1, 0)) << " " << int(img3.at<float>(1, 1)) << endl;

输出:

5 12
21 32

(4)除

一幅图像取反和另一幅图像相乘

image3 = image/image2

4,综合运算

(1)加权平均

image3 = image*0.5+image2*0.5
image3 = cv2.cvtColor(numpy.uint8(image3),cv2.CV_8UC1)

image3 = image*0.3+image2*0.7
image3 = cv2.cvtColor(numpy.uint8(image3),cv2.CV_8UC1)

四,直方图均衡、匹配

Opencv源码解析

12455
21255
34126
67772

1,直方图表示方法

(1)像素个数

ps:常见的L=256

(2)像素密度(离散)

 (3)像素密度(连续)

可以用一个定积分为1的曲线来描述像素密度,它和离散数据的折线图是相吻合的。

 

统计函数:

class EqualizeHistCalcHist_Invoker : public cv::ParallelLoopBody
{
//https://blog.csdn.net/nameofcsdn/article/details/120968236
};

int main()
{
	Mat src = imread("D:/img.PNG",0);
	Mutex histogramLockInstance;
	const int hist_sz = EqualizeHistCalcHist_Invoker::HIST_SZ;
	int hist[hist_sz] = { 0, };
	EqualizeHistCalcHist_Invoker calcBody(src, hist, &histogramLockInstance);
	cv::Range heightRange(0, src.rows);
	calcBody(heightRange);
	//cv::waitKey(0);
	return 0;
}

heightRange是行的范围,支持只统计若干行的直方图。

src是输入图像,hist是出参直方图,仿函数是直方图统计。

2,直方图均衡

基本思想是把原始图的直方图变换为均匀分布的形式,这样就增加了像素灰度值的动态范围,从而达到增强图像整体对比度的效果。

(1)直方图均衡的条件

一个灰度变换操作是直方图均衡的充分必要条件是,它满足如下几个要求:

  • 首先,直方图均衡应该是一个函数,即一个灰度不可能一部分变成灰度1,另一部分变成灰度2,这是没法实现的。
  • 其次,我们不要求它是单射,因为这样对于一些图像就无法调整了。比如有256个像素点分别是0-255,剩下的几千个像素点都是150-160,那么直方图均衡可以把150-160拉开距离,而把很低的和很高的像素值合并。
  • 再其次,这个映射函数应该是单调递增函数,直方图均衡需要保证较亮的区域调整后还是较亮。
  • 最后,变换后的直方图应该非常接近均衡(折线图接近水平直线)。

(2)直方图的最大均衡

在满足上述条件的情况下,我们有一个常用的直方图均衡操作。

(2.1)书上的推导方法

ps:当前场景是单调函数,所以可以省掉绝对值。

 结合这2个式子很容易证明,上述式子满足直方图均衡的条件,且s的概率密度函数是个完美的水平直线,所以这是最高程度的均衡。

(2.2)我的推导方法

书上的方法是直接给答案s=......,然后经过验证,这是最高均衡。

我的方法是根据最高均衡的条件推出答案。

有一个和上面的式(3.3-3)等价的表达方式:

\int _0^rp_rdr=\int _0^sp_sds

我觉得这才是原始条件,根据积分上限函数的倒数,可以直接推出式(3.3-3)

现在,我们直接取最高均衡p_s=\frac{1}{L-1}

则上式化成s=(L-1)\int _0^rp_rdr

(2.3)把连续的公式转化成离散的

总结:输出图像只由概率密度函数决定,最大均衡的概率密度函数是水平直线

(2.4)公式不足

s的最大值总是能取到L-1的,但是最小值取决于最低灰度的像素数,如果这一级的像素数超过一半,那么s的最小值就超过了L/2。

库函数会把最低的灰度变成0,剩下其他的灰度用公式计算。

(3)库函数

python版:

image2=cv2.equalizeHist(image)

C++版:

我们可以把一个图二值化为只有0和1,这样的图看起来是全黑的,需要直方图均衡才能较好的显示:

int main()
{
	Mat img = imread("D:/pw.png", 0);
	Mat img2, img3;
	threshold(img, img2, 100, 1, THRESH_BINARY);
	equalizeHist(img2, img3);
	cv::imshow("img", img);
	cv::imshow("img2", img2);
	cv::imshow("img3", img3);
	cv::waitKey(0);
	return 0;
}

 ps:最后的图像不圆滑,是二值化时造成的,不是直方图均衡时造成的。

3,直方图匹配

如果我们想要某种特定的均衡(不一定要最均衡),那就需要直方图匹配,也叫直方图规定化。

p_z(z)是我们指定的输出图像的概率密度函数,那么输出图像只由p_z(z)决定。

p_z(z)  没有限制条件,所以输出图像可能比原图更均衡,也可能更不均衡,甚至可能变成纯色图。

原图、直方图均衡输出图、直方图匹配输出图三者之间都可以根据概率密度函数来互相转化

\int _0^rp_rdr=\int _0^sp_sds

只要知道了pr和ps,就可以求出s,如果ps是水平直线,那就是直方图均衡。

void MarchHist(InputArray _src, InputArray _src2, OutputArray _dst)
{
	CV_Assert(_src.type() == CV_8UC1);
	if (_src.empty())
		return;
	Mat src = _src.getMat();
	Mat src2 = _src2.getMat();
	_dst.create(src.size(), src.type());
	Mat dst = _dst.getMat();

	Mutex histogramLockInstance;
	Mutex histogramLockInstance2;
	const int hist_sz = EqualizeHistCalcHist_Invoker::HIST_SZ;
	int hist1[hist_sz] = { 0 };
	int hist2[hist_sz] = { 0 };
	int lut[hist_sz];

	EqualizeHistCalcHist_Invoker calcBody(src, hist1, &histogramLockInstance);
	EqualizeHistCalcHist_Invoker calcBody2(src2, hist2, &histogramLockInstance2);
	EqualizeHistLut_Invoker      lutBody(src, dst, lut);
	cv::Range heightRange(0, src.rows);
	cv::Range heightRange2(0, src2.rows);

	if (EqualizeHistCalcHist_Invoker::isWorthParallel(src))
		parallel_for_(heightRange, calcBody);
	else
		calcBody(heightRange);
	if (EqualizeHistCalcHist_Invoker::isWorthParallel(src2))
		parallel_for_(heightRange2, calcBody2);
	else
		calcBody2(heightRange2);

	int s = hist2[0] - hist1[0];
	for (int i = 0, j = 0; i < hist_sz && j < hist_sz;) { // 双指针
		lut[i] = j;
		if (s <= 0) {
			s += hist2[++j];
		}
		else {
			s -= hist1[++i];
		}
	}

	lutBody(heightRange);
}

int main()
{
	Mat img = imread("D:/img5.png", 0);
	Mat img2 = imread("D:/img4.png", 0);
	Mat img3;
	MarchHist(img, img2,img3);
	cv::imshow("img", img);
	cv::imshow("img2", img2);
	cv::imshow("img3", img3);
	cv::waitKey(0);
	return 0;
}

4,局部直方图处理

参考https://zhuanlan.zhihu.com/p/259886947

五,二值化

opencv提供的函数:

double threshold( InputArray src, OutputArray dst,
                               double thresh, double maxval, int type );

enum ThresholdTypes {
    THRESH_BINARY     = 0, //!< \f[\texttt{dst} (x,y) =  \fork{\texttt{maxval}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f]
    THRESH_BINARY_INV = 1, //!< \f[\texttt{dst} (x,y) =  \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{maxval}}{otherwise}\f]
    THRESH_TRUNC      = 2, //!< \f[\texttt{dst} (x,y) =  \fork{\texttt{threshold}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f]
    THRESH_TOZERO     = 3, //!< \f[\texttt{dst} (x,y) =  \fork{\texttt{src}(x,y)}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f]
    THRESH_TOZERO_INV = 4, //!< \f[\texttt{dst} (x,y) =  \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f]
    THRESH_MASK       = 7,
    THRESH_OTSU       = 8, //!< flag, use Otsu algorithm to choose the optimal threshold value
    THRESH_TRIANGLE   = 16 //!< flag, use Triangle algorithm to choose the optimal threshold value
};

前五个是全局阈值:

THRESH_BINARY:如果 src(x,y)>threshold ,dst(x,y) = max_value; 否则,dst(x,y)=0;
THRESH_BINARY_INV:如果 src(x,y)>threshold,dst(x,y) = 0; 否则,dst(x,y) = max_value.
THRESH_TRUNC:如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y).
THRESH_TOZERO:如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否则 dst(x,y) = 0。
THRESH_TOZERO_INV:如果 src(x,y)>threshold,dst(x,y) = 0 ; 否则dst(x,y) = src(x,y).
 

使用方法:

int main()
{
	Mat img = imread("D:/1.png", 0);
	Mat img2;
	threshold(img, img2, 100, 255, THRESH_BINARY);
	cv::imshow("img", img);
	cv::imshow("img2", img2);
	cv::waitKey(0);
	return 0;
}

可以看到一些线条丢失了,不同的地方亮度不同,很容易顾此失彼。

如果换成局部阈值THRESH_OTSU或者THRESH_TRIANGLE,运行效果:

 因为右下角比较黑所以这块都黑了。

六,空域方法——模板处理

利用空间滤波器处理图像。

空间滤波器_nameofcsdn的博客-CSDN博客_滤波器csdn

七,频域方法

傅里叶变换 https://blog.csdn.net/nameofcsdn/article/details/118400482

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值