旋转图像并裁剪黑色边框

我的应用程序:我想旋转一个图像(使用OpenCV和Python)

旋转图像

目前,我已经开发了旋转input图像的下面的代码,用黑色边框填充它,给我答:我想要的是B-旋转图像中最大的可能区域裁剪窗口。 我称之为轴alignment边界框。

这与旋转和裁剪基本相同,但是我无法得到有关该问题的答案。 此外,答案显然只适用于方形图像。 我的图像是矩形的。

代码给A:

import cv2 import numpy as np def getTranslationMatrix2d(dx, dy): """ Returns a numpy affine transformation matrix for a 2D translation of (dx, dy) """ return np.matrix([[1, 0, dx], [0, 1, dy], [0, 0, 1]]) def rotateImage(image, angle): """ Rotates the given image about it's centre """ image_size = (image.shape[1], image.shape[0]) image_center = tuple(np.array(image_size) / 2) rot_mat = np.vstack([cv2.getRotationMatrix2D(image_center, angle, 1.0), [0, 0, 1]]) trans_mat = np.identity(3) w2 = image_size[0] * 0.5 h2 = image_size[1] * 0.5 rot_mat_notranslate = np.matrix(rot_mat[0:2, 0:2]) tl = (np.array([-w2, h2]) * rot_mat_notranslate).A[0] tr = (np.array([w2, h2]) * rot_mat_notranslate).A[0] bl = (np.array([-w2, -h2]) * rot_mat_notranslate).A[0] br = (np.array([w2, -h2]) * rot_mat_notranslate).A[0] x_coords = [pt[0] for pt in [tl, tr, bl, br]] x_pos = [x for x in x_coords if x > 0] x_neg = [x for x in x_coords if x < 0] y_coords = [pt[1] for pt in [tl, tr, bl, br]] y_pos = [y for y in y_coords if y > 0] y_neg = [y for y in y_coords if y < 0] right_bound = max(x_pos) left_bound = min(x_neg) top_bound = max(y_pos) bot_bound = min(y_neg) new_w = int(abs(right_bound - left_bound)) new_h = int(abs(top_bound - bot_bound)) new_image_size = (new_w, new_h) new_midx = new_w * 0.5 new_midy = new_h * 0.5 dx = int(new_midx - w2) dy = int(new_midy - h2) trans_mat = getTranslationMatrix2d(dx, dy) affine_mat = (np.matrix(trans_mat) * np.matrix(rot_mat))[0:2, :] result = cv2.warpAffine(image, affine_mat, new_image_size, flags=cv2.INTER_LINEAR) return result 

这个解决scheme/实现背后的math相当于这个类似问题的解决scheme ,但是公式被简化并避免了奇点。 这是与其他解决scheme中具有相同接口的python代码,但是在几乎所有情况下都提供了更大的区域(始终是最佳的certificate):

 def rotatedRectWithMaxArea(w, h, angle): """ Given a rectangle of size wxh that has been rotated by 'angle' (in radians), computes the width and height of the largest possible axis-aligned rectangle (maximal area) within the rotated rectangle. """ if w <= 0 or h <= 0: return 0,0 width_is_longer = w >= h side_long, side_short = (w,h) if width_is_longer else (h,w) # since the solutions for angle, -angle and 180-angle are all the same, # if suffices to look at the first quadrant and the absolute values of sin,cos: sin_a, cos_a = abs(math.sin(angle)), abs(math.cos(angle)) if side_short <= 2.*sin_a*cos_a*side_long: # half constrained case: two crop corners touch the longer side, # the other two corners are on the mid-line parallel to the longer line x = 0.5*side_short wr,hr = (x/sin_a,x/cos_a) if width_is_longer else (x/cos_a,x/sin_a) else: # fully constrained case: crop touches all 4 sides cos_2a = cos_a*cos_a - sin_a*sin_a wr,hr = (w*cos_a - h*sin_a)/cos_2a, (h*cos_a - w*sin_a)/cos_2a return wr,hr 

下面是该function与其他解决scheme的比较:

 >>> wl,hl = largest_rotated_rect(1500,500,math.radians(20)) >>> print (wl,hl),', area=',wl*hl (828.2888697391496, 230.61639227890998) , area= 191016.990904 >>> wm,hm = rotatedRectWithMaxArea(1500,500,math.radians(20)) >>> print (wm,hm),', area=',wm*hm (730.9511000407718, 266.044443118978) , area= 194465.478358 

certificate:

寻找具有最大面积的两条平行线之间的轴alignment的矩形是一个参数的优化问题,例如,如图中的x动画参数

s表示两条平行线之间的距离(它将变成旋转矩形的较短边)。 那么所寻求的矩形的边abxsx ,即x = a sinα和(sx)= b cosα具有恒定的比率:

在这里输入图像说明

所以最大化面积a*b意味着最大化x*(sx) 。 由于直angular三angular形的“高度定理”,我们知道x*(sx) = p*q = h*h 。 因此,在x = sx = s/2处达到最大面积,即平行线之间的两个angularE,G在中线上:

在这里输入图像说明

这个解决scheme只有在这个最大的矩形适合旋转的矩形时才有效。 因此,对angular线EG不得长于旋转矩形的另一边L. 以来

EG = AF + DH = s / 2 *(cotα+tanα)= s /(2 * sinαcosα)= s / sin

我们有条件s≤l sin 2α,其中s和l是旋转矩形的较短和较长边。

在s> l sin 2α的情况下,参数x必须小于(s / 2),并且所寻求的矩形的所有angular落都在旋转的矩形的一侧上。 这导致了等式

x *cotα+(sx)*tanα= 1

给出x =sinα lcosα- ssinα)/ cos 2α。 从a = x / sinα和b =(sx)/ cosα我们可以得到上面使用的公式。

所以,在调查了很多声称的解决scheme之后,我终于find了一个可行的方法。 Andri和Magnus Hoff的答案是在一个旋转矩形中计算最大的矩形 。

下面的Python代码包含感兴趣的方法largest_rotated_rect – 和一个简短的演示。

 import math import cv2 import numpy as np def rotate_image(image, angle): """ Rotates an OpenCV 2 / NumPy image about it's centre by the given angle (in degrees). The returned image will be large enough to hold the entire new image, with a black background """ # Get the image size # No that's not an error - NumPy stores image matricies backwards image_size = (image.shape[1], image.shape[0]) image_center = tuple(np.array(image_size) / 2) # Convert the OpenCV 3x2 rotation matrix to 3x3 rot_mat = np.vstack( [cv2.getRotationMatrix2D(image_center, angle, 1.0), [0, 0, 1]] ) rot_mat_notranslate = np.matrix(rot_mat[0:2, 0:2]) # Shorthand for below calcs image_w2 = image_size[0] * 0.5 image_h2 = image_size[1] * 0.5 # Obtain the rotated coordinates of the image corners rotated_coords = [ (np.array([-image_w2, image_h2]) * rot_mat_notranslate).A[0], (np.array([ image_w2, image_h2]) * rot_mat_notranslate).A[0], (np.array([-image_w2, -image_h2]) * rot_mat_notranslate).A[0], (np.array([ image_w2, -image_h2]) * rot_mat_notranslate).A[0] ] # Find the size of the new image x_coords = [pt[0] for pt in rotated_coords] x_pos = [x for x in x_coords if x > 0] x_neg = [x for x in x_coords if x < 0] y_coords = [pt[1] for pt in rotated_coords] y_pos = [y for y in y_coords if y > 0] y_neg = [y for y in y_coords if y < 0] right_bound = max(x_pos) left_bound = min(x_neg) top_bound = max(y_pos) bot_bound = min(y_neg) new_w = int(abs(right_bound - left_bound)) new_h = int(abs(top_bound - bot_bound)) # We require a translation matrix to keep the image centred trans_mat = np.matrix([ [1, 0, int(new_w * 0.5 - image_w2)], [0, 1, int(new_h * 0.5 - image_h2)], [0, 0, 1] ]) # Compute the tranform for the combined rotation and translation affine_mat = (np.matrix(trans_mat) * np.matrix(rot_mat))[0:2, :] # Apply the transform result = cv2.warpAffine( image, affine_mat, (new_w, new_h), flags=cv2.INTER_LINEAR ) return result def largest_rotated_rect(w, h, angle): """ Given a rectangle of size wxh that has been rotated by 'angle' (in radians), computes the width and height of the largest possible axis-aligned rectangle within the rotated rectangle. Original JS code by 'Andri' and Magnus Hoff from Stack Overflow Converted to Python by Aaron Snoswell """ quadrant = int(math.floor(angle / (math.pi / 2))) & 3 sign_alpha = angle if ((quadrant & 1) == 0) else math.pi - angle alpha = (sign_alpha % math.pi + math.pi) % math.pi bb_w = w * math.cos(alpha) + h * math.sin(alpha) bb_h = w * math.sin(alpha) + h * math.cos(alpha) gamma = math.atan2(bb_w, bb_w) if (w < h) else math.atan2(bb_w, bb_w) delta = math.pi - alpha - gamma length = h if (w < h) else w d = length * math.cos(alpha) a = d * math.sin(alpha) / math.sin(delta) y = a * math.cos(gamma) x = y * math.tan(gamma) return ( bb_w - 2 * x, bb_h - 2 * y ) def crop_around_center(image, width, height): """ Given a NumPy / OpenCV 2 image, crops it to the given width and height, around it's centre point """ image_size = (image.shape[1], image.shape[0]) image_center = (int(image_size[0] * 0.5), int(image_size[1] * 0.5)) if(width > image_size[0]): width = image_size[0] if(height > image_size[1]): height = image_size[1] x1 = int(image_center[0] - width * 0.5) x2 = int(image_center[0] + width * 0.5) y1 = int(image_center[1] - height * 0.5) y2 = int(image_center[1] + height * 0.5) return image[y1:y2, x1:x2] def demo(): """ Demos the largest_rotated_rect function """ image = cv2.imread("lenna_rectangle.png") image_height, image_width = image.shape[0:2] cv2.imshow("Original Image", image) print "Press [enter] to begin the demo" print "Press [q] or Escape to quit" key = cv2.waitKey(0) if key == ord("q") or key == 27: exit() for i in np.arange(0, 360, 0.5): image_orig = np.copy(image) image_rotated = rotate_image(image, i) image_rotated_cropped = crop_around_center( image_rotated, *largest_rotated_rect( image_width, image_height, math.radians(i) ) ) key = cv2.waitKey(2) if(key == ord("q") or key == 27): exit() cv2.imshow("Original Image", image_orig) cv2.imshow("Rotated Image", image_rotated) cv2.imshow("Cropped Image", image_rotated_cropped) print "Done" if __name__ == "__main__": demo() 

图像旋转演示

只需将该图像 (裁剪以表明它与非方形图像一起使用)放在与上述文件相同的目录中,然后运行它。

祝贺这项伟大的工作! 我想在C ++库中使用OpenCV中的代码,所以我做了下面的转换。 也许这种方法可能对其他人有帮助。

 #include <iostream> #include <opencv.hpp> #define PI 3.14159265359 using namespace std; double degree_to_radian(double angle) { return angle * PI / 180; } cv::Mat rotate_image (cv::Mat image, double angle) { // Rotates an OpenCV 2 image about its centre by the given angle // (in radians). The returned image will be large enough to hold the entire // new image, with a black background cv::Size image_size = cv::Size(image.rows, image.cols); cv::Point image_center = cv::Point(image_size.height/2, image_size.width/2); // Convert the OpenCV 3x2 matrix to 3x3 cv::Mat rot_mat = cv::getRotationMatrix2D(image_center, angle, 1.0); double row[3] = {0.0, 0.0, 1.0}; cv::Mat new_row = cv::Mat(1, 3, rot_mat.type(), row); rot_mat.push_back(new_row); double slice_mat[2][2] = { {rot_mat.col(0).at<double>(0), rot_mat.col(1).at<double>(0)}, {rot_mat.col(0).at<double>(1), rot_mat.col(1).at<double>(1)} }; cv::Mat rot_mat_nontranslate = cv::Mat(2, 2, rot_mat.type(), slice_mat); double image_w2 = image_size.width * 0.5; double image_h2 = image_size.height * 0.5; // Obtain the rotated coordinates of the image corners std::vector<cv::Mat> rotated_coords; double image_dim_d_1[2] = { -image_h2, image_w2 }; cv::Mat image_dim = cv::Mat(1, 2, rot_mat.type(), image_dim_d_1); rotated_coords.push_back(cv::Mat(image_dim * rot_mat_nontranslate)); double image_dim_d_2[2] = { image_h2, image_w2 }; image_dim = cv::Mat(1, 2, rot_mat.type(), image_dim_d_2); rotated_coords.push_back(cv::Mat(image_dim * rot_mat_nontranslate)); double image_dim_d_3[2] = { -image_h2, -image_w2 }; image_dim = cv::Mat(1, 2, rot_mat.type(), image_dim_d_3); rotated_coords.push_back(cv::Mat(image_dim * rot_mat_nontranslate)); double image_dim_d_4[2] = { image_h2, -image_w2 }; image_dim = cv::Mat(1, 2, rot_mat.type(), image_dim_d_4); rotated_coords.push_back(cv::Mat(image_dim * rot_mat_nontranslate)); // Find the size of the new image vector<double> x_coords, x_pos, x_neg; for (int i = 0; i < rotated_coords.size(); i++) { double pt = rotated_coords[i].col(0).at<double>(0); x_coords.push_back(pt); if (pt > 0) x_pos.push_back(pt); else x_neg.push_back(pt); } vector<double> y_coords, y_pos, y_neg; for (int i = 0; i < rotated_coords.size(); i++) { double pt = rotated_coords[i].col(1).at<double>(0); y_coords.push_back(pt); if (pt > 0) y_pos.push_back(pt); else y_neg.push_back(pt); } double right_bound = *max_element(x_pos.begin(), x_pos.end()); double left_bound = *min_element(x_neg.begin(), x_neg.end()); double top_bound = *max_element(y_pos.begin(), y_pos.end()); double bottom_bound = *min_element(y_neg.begin(), y_neg.end()); int new_w = int(abs(right_bound - left_bound)); int new_h = int(abs(top_bound - bottom_bound)); // We require a translation matrix to keep the image centred double trans_mat[3][3] = { {1, 0, int(new_w * 0.5 - image_w2)}, {0, 1, int(new_h * 0.5 - image_h2)}, {0, 0, 1}, }; // Compute the transform for the combined rotation and translation cv::Mat aux_affine_mat = (cv::Mat(3, 3, rot_mat.type(), trans_mat) * rot_mat); cv::Mat affine_mat = cv::Mat(2, 3, rot_mat.type(), NULL); affine_mat.push_back(aux_affine_mat.row(0)); affine_mat.push_back(aux_affine_mat.row(1)); // Apply the transform cv::Mat output; cv::warpAffine(image, output, affine_mat, cv::Size(new_h, new_w), cv::INTER_LINEAR); return output; } cv::Size largest_rotated_rect(int h, int w, double angle) { // Given a rectangle of size wxh that has been rotated by 'angle' (in // radians), computes the width and height of the largest possible // axis-aligned rectangle within the rotated rectangle. // Original JS code by 'Andri' and Magnus Hoff from Stack Overflow // Converted to Python by Aaron Snoswell (https://stackoverflow.com/questions/16702966/rotate-image-and-crop-out-black-borders) // Converted to C++ by Eliezer Bernart int quadrant = int(floor(angle/(PI/2))) & 3; double sign_alpha = ((quadrant & 1) == 0) ? angle : PI - angle; double alpha = fmod((fmod(sign_alpha, PI) + PI), PI); double bb_w = w * cos(alpha) + h * sin(alpha); double bb_h = w * sin(alpha) + h * cos(alpha); double gamma = w < h ? atan2(bb_w, bb_w) : atan2(bb_h, bb_h); double delta = PI - alpha - gamma; int length = w < h ? h : w; double d = length * cos(alpha); double a = d * sin(alpha) / sin(delta); double y = a * cos(gamma); double x = y * tan(gamma); return cv::Size(bb_w - 2 * x, bb_h - 2 * y); } // for those interested in the actual optimum - contributed by coproc #include <algorithm> cv::Size really_largest_rotated_rect(int h, int w, double angle) { // Given a rectangle of size wxh that has been rotated by 'angle' (in // radians), computes the width and height of the largest possible // axis-aligned rectangle within the rotated rectangle. if (w <= 0 || h <= 0) return cv::Size(0,0); bool width_is_longer = w >= h; int side_long = w, side_short = h; if (!width_is_longer) std::swap(side_long, side_short); // since the solutions for angle, -angle and pi-angle are all the same, // it suffices to look at the first quadrant and the absolute values of sin,cos: double sin_a = fabs(math.sin(angle)), cos_a = fabs(math.cos(angle)); double wr,hr; if (side_short <= 2.*sin_a*cos_a*side_long) { // half constrained case: two crop corners touch the longer side, // the other two corners are on the mid-line parallel to the longer line x = 0.5*side_short; wr = x/sin_a; hr = x/cos_a; if (!width_is_longer) std::swap(wr,hr); } else { // fully constrained case: crop touches all 4 sides double cos_2a = cos_a*cos_a - sin_a*sin_a; wr = (w*cos_a - h*sin_a)/cos_2a; hr = (h*cos_a - w*sin_a)/cos_2a; } return cv::Size(wr,hr); } cv::Mat crop_around_center(cv::Mat image, int height, int width) { // Given a OpenCV 2 image, crops it to the given width and height, // around it's centre point cv::Size image_size = cv::Size(image.rows, image.cols); cv::Point image_center = cv::Point(int(image_size.height * 0.5), int(image_size.width * 0.5)); if (width > image_size.width) width = image_size.width; if (height > image_size.height) height = image_size.height; int x1 = int(image_center.x - width * 0.5); int x2 = int(image_center.x + width * 0.5); int y1 = int(image_center.y - height * 0.5); int y2 = int(image_center.y + height * 0.5); return image(cv::Rect(cv::Point(y1, x1), cv::Point(y2,x2))); } void demo(cv::Mat image) { // Demos the largest_rotated_rect function int image_height = image.rows; int image_width = image.cols; for (float i = 0.0; i < 360.0; i+=0.5) { cv::Mat image_orig = image.clone(); cv::Mat image_rotated = rotate_image(image, i); cv::Size largest_rect = largest_rotated_rect(image_height, image_width, degree_to_radian(i)); // for those who trust math (added by coproc): cv::Size largest_rect2 = really_largest_rotated_rect(image_height, image_width, degree_to_radian(i)); cout << "area1 = " << largest_rect.height * largest_rect.width << endl; cout << "area2 = " << largest_rect2.height * largest_rect2.width << endl; cv::Mat image_rotated_cropped = crop_around_center( image_rotated, largest_rect.height, largest_rect.width ); cv::imshow("Original Image", image_orig); cv::imshow("Rotated Image", image_rotated); cv::imshow("Cropped image", image_rotated_cropped); if (char(cv::waitKey(15)) == 'q') break; } } int main (int argc, char* argv[]) { cv::Mat image = cv::imread(argv[1]); if (image.empty()) { cout << "> The input image was not found." << endl; exit(EXIT_FAILURE); } cout << "Press [s] to begin or restart the demo" << endl; cout << "Press [q] to quit" << endl; while (true) { cv::imshow("Original Image", image); char opt = char(cv::waitKey(0)); switch (opt) { case 's': demo(image); break; case 'q': return EXIT_SUCCESS; default: break; } } return EXIT_SUCCESS; } 

有一个简单的方法来处理这个问题,使用另一个称为PIL的模块(只有在没有使用opencv的情况下才有用)

下面的代码是完全一样的,并以任何图像的方式来滚动,以至于你不会得到黑色的像素

 from PIL import Image def array_to_img(x, scale=True): x = x.transpose(1, 2, 0) if scale: x += max(-np.min(x), 0) x /= np.max(x) x *= 255 if x.shape[2] == 3: return Image.fromarray(x.astype("uint8"), "RGB") else: return Image.fromarray(x[:,:,0].astype("uint8"), "L") def img_to_array(img): x = np.asarray(img, dtype='float32') if len(x.shape)==3: # RGB: height, width, channel -> channel, height, width x = x.transpose(2, 0, 1) else: # grayscale: height, width -> channel, height, width x = x.reshape((1, x.shape[0], x.shape[1])) return x if __name__ == "__main__": # Calls a function to convert image to array image_array = img_to_array(image_name) # Calls the function to rotate the image by given angle rotated_image = array_to_img(random_rotation(image_array, rotation_angle)) # give the location where you want to store the image rotated_image_name=<location_of_the_image_>+'roarted_image.png' # Saves the image in the mentioned location rotated_image.save(rotated_image_name) 
Interesting Posts