Skip to content

为图像添加边框

本教程中你将学到如何:

  • 使用 OpenCV 函数 copyMakeBorder() 来设置边框(为图像添加额外的填充)。

注: 下面的解释出自 Bradski 和 Kaehler 所著的 Learning OpenCV 一书。

  1. 在之前的教程中,我们学习了使用卷积对图像进行操作。一个自然产生的问题是如何处理边界:当被计算的点位于图像边缘时,我们如何进行卷积?

  2. 大多数 OpenCV 函数的做法是:把给定图像复制到另一幅稍大的图像上,然后自动对边界进行填充(使用下面示例代码中介绍的任意一种方法)。这样,卷积就可以顺利地在所需的像素上执行(操作完成后,多余的填充会被裁掉)。

  3. 在本教程中,我们将简要探讨为图像定义额外填充(边框)的两种方式:

    1. BORDER_CONSTANT:用一个常量值填充图像边框(例如黑色或 00)。
    2. BORDER_REPLICATE:把原图最边缘的行或列复制到额外的边框中。

    这在代码部分会看得更清楚。

  • 这个程序做什么?
    • 加载一幅图像

    • 让用户选择在输入图像上使用哪种填充方式。有两个选项:

      1. *常量值边框*:对整个边框应用一个常量值填充。该值每 0.5 秒随机更新一次。
      2. *复制边框*:边框将从原图边缘的像素值复制而来。
      用户通过按 `c`(constant)或 `r`(replicate)来选择任一选项。
    • 当用户按下 ESC 时程序结束。

教程代码如下所示。

C++

你也可以从此处下载。

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
// Declare the variables
Mat src, dst;
int top, bottom, left, right;
int borderType = BORDER_CONSTANT;
const char* window_name = "copyMakeBorder Demo";
RNG rng(12345);
int main( int argc, char** argv )
{
const char* imageName = argc >=2 ? argv[1] : "lena.jpg";
// Loads an image
src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
// Check if image is loaded fine
if( src.empty()) {
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default lena.jpg] \n");
return -1;
}
// Brief how-to for this program
printf( "\n \t copyMakeBorder Demo: \n" );
printf( "\t -------------------- \n" );
printf( " ** Press 'c' to set the border to a random constant value \n");
printf( " ** Press 'r' to set the border to be replicated \n");
printf( " ** Press 'ESC' to exit the program \n");
namedWindow( window_name, WINDOW_AUTOSIZE );
// Initialize arguments for the filter
top = (int) (0.05*src.rows); bottom = top;
left = (int) (0.05*src.cols); right = left;
for(;;)
{
Scalar value( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
imshow( window_name, dst );
char c = (char)waitKey(500);
if( c == 27 )
{ break; }
else if( c == 'c' )
{ borderType = BORDER_CONSTANT; }
else if( c == 'r' )
{ borderType = BORDER_REPLICATE; }
}
return 0;
}

Java

你也可以从此处下载。

import org.opencv.core.*;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import java.util.Random;
class CopyMakeBorderRun {
public void run(String[] args) {
// Declare the variables
Mat src, dst = new Mat();
int top, bottom, left, right;
int borderType = Core.BORDER_CONSTANT;
String window_name = "copyMakeBorder Demo";
Random rng;
String imageName = ((args.length > 0) ? args[0] : "../data/lena.jpg");
// Load an image
src = Imgcodecs.imread(imageName, Imgcodecs.IMREAD_COLOR);
// Check if image is loaded fine
if( src.empty() ) {
System.out.println("Error opening image!");
System.out.println("Program Arguments: [image_name -- default ../data/lena.jpg] \n");
System.exit(-1);
}
// Brief how-to for this program
System.out.println("\n" +
"\t copyMakeBorder Demo: \n" +
"\t -------------------- \n" +
" ** Press 'c' to set the border to a random constant value \n" +
" ** Press 'r' to set the border to be replicated \n" +
" ** Press 'ESC' to exit the program \n");
HighGui.namedWindow( window_name, HighGui.WINDOW_AUTOSIZE );
// Initialize arguments for the filter
top = (int) (0.05*src.rows()); bottom = top;
left = (int) (0.05*src.cols()); right = left;
while( true ) {
rng = new Random();
Scalar value = new Scalar( rng.nextInt(256),
rng.nextInt(256), rng.nextInt(256) );
Core.copyMakeBorder( src, dst, top, bottom, left, right, borderType, value);
HighGui.imshow( window_name, dst );
char c = (char) HighGui.waitKey(500);
c = Character.toLowerCase(c);
if( c == 27 )
{ break; }
else if( c == 'c' )
{ borderType = Core.BORDER_CONSTANT;}
else if( c == 'r' )
{ borderType = Core.BORDER_REPLICATE;}
}
System.exit(0);
}
}
public class CopyMakeBorder {
public static void main(String[] args) {
// Load the native library.
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new CopyMakeBorderRun().run(args);
}
}

Python

你也可以从此处下载。

import sys
from random import randint
import cv2 as cv
def main(argv):
# First we declare the variables we are going to use
borderType = cv.BORDER_CONSTANT
window_name = "copyMakeBorder Demo"
imageName = argv[0] if len(argv) > 0 else 'lena.jpg'
# Loads an image
src = cv.imread(cv.samples.findFile(imageName), cv.IMREAD_COLOR)
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: copy_make_border.py [image_name -- default lena.jpg] \n')
return -1
# Brief how-to for this program
print ('\n'
'\t copyMakeBorder Demo: \n'
' -------------------- \n'
' ** Press \'c\' to set the border to a random constant value \n'
' ** Press \'r\' to set the border to be replicated \n'
' ** Press \'ESC\' to exit the program ')
cv.namedWindow(window_name, cv.WINDOW_AUTOSIZE)
# Initialize arguments for the filter
top = int(0.05 * src.shape[0]) # shape[0] = rows
bottom = top
left = int(0.05 * src.shape[1]) # shape[1] = cols
right = left
while 1:
value = [randint(0, 255), randint(0, 255), randint(0, 255)]
dst = cv.copyMakeBorder(src, top, bottom, left, right, borderType, None, value)
cv.imshow(window_name, dst)
c = cv.waitKey(500)
if c == 27:
break
elif c == 99: # 99 = ord('c')
borderType = cv.BORDER_CONSTANT
elif c == 114: # 114 = ord('r')
borderType = cv.BORDER_REPLICATE
return 0
if __name__ == "__main__":
main(sys.argv[1:])

首先我们声明要用到的变量:

C++

// Declare the variables
Mat src, dst;
int top, bottom, left, right;
int borderType = BORDER_CONSTANT;
const char* window_name = "copyMakeBorder Demo";
RNG rng(12345);

Java

// Declare the variables
Mat src, dst = new Mat();
int top, bottom, left, right;
int borderType = Core.BORDER_CONSTANT;
String window_name = "copyMakeBorder Demo";
Random rng;

Python

borderType = cv.BORDER_CONSTANT
window_name = "copyMakeBorder Demo"

特别值得注意的是变量 rng,它是一个随机数生成器。我们用它生成随机的边框颜色,稍后就会看到。

像往常一样,我们加载源图像 src:

C++

const char* imageName = argc >=2 ? argv[1] : "lena.jpg";
// Loads an image
src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
// Check if image is loaded fine
if( src.empty()) {
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default lena.jpg] \n");
return -1;
}

Java

String imageName = ((args.length > 0) ? args[0] : "../data/lena.jpg");
// Load an image
src = Imgcodecs.imread(imageName, Imgcodecs.IMREAD_COLOR);
// Check if image is loaded fine
if( src.empty() ) {
System.out.println("Error opening image!");
System.out.println("Program Arguments: [image_name -- default ../data/lena.jpg] \n");
System.exit(-1);
}

Python

imageName = argv[0] if len(argv) > 0 else 'lena.jpg'
# Loads an image
src = cv.imread(cv.samples.findFile(imageName), cv.IMREAD_COLOR)
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: copy_make_border.py [image_name -- default lena.jpg] \n')
return -1

在简短介绍如何使用本程序之后,我们创建一个窗口:

C++

namedWindow( window_name, WINDOW_AUTOSIZE );

Java

HighGui.namedWindow( window_name, HighGui.WINDOW_AUTOSIZE );

Python

cv.namedWindow(window_name, cv.WINDOW_AUTOSIZE)

现在我们初始化定义边框大小的参数(top、bottom、left 和 right)。我们把它们设为 src 尺寸的 5%:

C++

// Initialize arguments for the filter
top = (int) (0.05*src.rows); bottom = top;
left = (int) (0.05*src.cols); right = left;

Java

// Initialize arguments for the filter
top = (int) (0.05*src.rows()); bottom = top;
left = (int) (0.05*src.cols()); right = left;

Python

# Initialize arguments for the filter
top = int(0.05 * src.shape[0]) # shape[0] = rows
bottom = top
left = int(0.05 * src.shape[1]) # shape[1] = cols
right = left

程序在一个无限循环中运行,直到按下 ESC 键。如果用户按下 c 或 r,borderType 变量将分别取 BORDER_CONSTANT 或 BORDER_REPLICATE 的值:

C++

char c = (char)waitKey(500);
if( c == 27 )
{ break; }
else if( c == 'c' )
{ borderType = BORDER_CONSTANT; }
else if( c == 'r' )
{ borderType = BORDER_REPLICATE; }

Java

char c = (char) HighGui.waitKey(500);
c = Character.toLowerCase(c);
if( c == 27 )
{ break; }
else if( c == 'c' )
{ borderType = Core.BORDER_CONSTANT;}
else if( c == 'r' )
{ borderType = Core.BORDER_REPLICATE;}

Python

c = cv.waitKey(500)
if c == 27:
break
elif c == 99: # 99 = ord('c')
borderType = cv.BORDER_CONSTANT
elif c == 114: # 114 = ord('r')
borderType = cv.BORDER_REPLICATE

每次迭代(每 0.5 秒),随机边框颜色(value)都会被更新:

C++

Scalar value( rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255) );

Java

rng = new Random();
Scalar value = new Scalar( rng.nextInt(256),
rng.nextInt(256), rng.nextInt(256) );

Python

value = [randint(0, 255), randint(0, 255), randint(0, 255)]

这个值是在 [0,255][0, 255] 范围内随机选取的三个数。

最后,我们调用函数 copyMakeBorder() 来应用相应的填充:

C++

copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );

Java

Core.copyMakeBorder( src, dst, top, bottom, left, right, borderType, value);

Python

dst = cv.copyMakeBorder(src, top, bottom, left, right, borderType, None, value)
  • 各参数为:

    1. src:源图像
    2. dst:目标图像
    3. top、bottom、left、right:图像各侧边框的像素长度。我们把它们定义为原图尺寸的 5%。
    4. borderType:定义应用哪种类型的边框。本例中可以是 constant 或 replicate。
    5. value:如果 borderType 是 BORDER_CONSTANT,这就是用于填充边框像素的值。

我们在先前创建的窗口中显示输出图像:

C++

imshow( window_name, dst );

Java

HighGui.imshow( window_name, dst );

Python

cv.imshow(window_name, dst)

CopyMakeBorder_Tutorial_Results.jpg

  1. 编译上面的代码后,你可以以图像路径为参数运行它。结果应当是:

    • 默认情况下,程序以 BORDER_CONSTANT 边框开始。因此,你会看到一系列随机颜色的边框。
    • 如果你按 r,边框将变成边缘像素的复制。
    • 如果你按 c,随机颜色边框会再次出现。
    • 如果你按 ESC,程序将退出。